1 /* xscreensaver, Copyright © 2018-2022 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* The value of XScreenSaver's font resource strings is a comma-separated
13 list of font names that look like this:
15 Helvetica Bold Italic 12
17 We parse that into an XLFD and pass that along to the underlying systems:
18 XftFontOpenXlfd or jwxyz.
20 Xft does aggressive substitution of unknown fonts, so if we don't get an
21 exact match on the font name, we move on to the next element in the list.
22 For the last element, we take whatever substitution we got.
24 XftNameParse uses this syntax:
26 "Helvetica Neue-12:style=Bold"
27 "Helvetica Neue-12:style=Bold Italic"
28 "Helvetica Neue-12:slant=110:weight=200"
30 Cocoa uses PostScript names, with hyphens:
32 [NSFont fontWithName:@"Helvetica-BoldOblique" size:12];
33 [NSFont fontWithName:@"Times-Roman" size:12];
35 Alternately, with separated styles:
37 [[NSFontManager sharedFontManager]
38 fontWithFamily:@"Helvetica" traits:NSBoldFontMask weight:5 size:12];
40 Android separates names from styles:
42 Paint.setTypeface (Typeface.create ("Helvetica", Typeface.BOLD));
43 Paint.setTextSize (12);
45 See android/xscreensaver/src/org/jwz/xscreensaver/jwxyz.java
48 In XScreenSaver 6, USE_XFT is always true, as all programs now use Xft.
50 In XScreenSaver 5, this file was compiled in two different ways:
51 - As font-retry.o for programs that did not link with libXft;
52 - As font-retry-xft.o for programs that did.
54 #ifdef USE_XFT is whether Xft code should be called.
55 #ifdef HAVE_XFT is whether libXft is natively available.
57 The reason there are two defines is that if HAVE_XFT is not defined,
58 the Xft API is still available through emulation provided by "xft.h".
61 #define _GNU_SOURCE /* Why is this here? */
66 #include "font-retry.h"
68 extern const char *progname;
71 #define countof(x) (sizeof((x))/sizeof((*x)))
81 static void xft_selftest (Display *dpy, int screen);
85 /* Parse font names of the form "Helvetica Neue Bold Italic 12".
88 font_name_to_xlfd (const char *font_name)
90 char *name = strdup (font_name);
92 char *s, *s2, *b, *i, *o, c;
95 if (name[0] == '-' || name[0] == '*')
96 return name; /* Already an XLFD */
98 /* Downcase ASCII; dash to space */
99 for (s = name; *s; s++)
100 if (*s >= 'A' && *s <= 'Z')
105 /* "Family NN" or "Family-NN" */
106 s = strrchr (name, ' ');
107 s2 = strrchr (name, '-');
108 if (s && s2 && s2 > s) s = s2;
110 if (1 != sscanf (s+1, " %f %c", &size, &c)) goto FAIL;
111 if (size <= 0) goto FAIL;
114 /* "Family Bold", etc. */
115 b = strstr (name, " bold");
116 i = strstr (name, " italic");
117 o = strstr (name, " oblique");
122 xlfd = (char *) malloc (strlen(name) + 80);
123 sprintf (xlfd, "-*-%s-%s-%s-*-*-*-%d-*-*-*-*-*-*",
125 (b ? "bold" : "medium"),
126 (i ? "i" : o ? "o" : "r"),
132 fprintf (stderr, "%s: XFT: unparsable: \"%s\"\n", progname, font_name);
133 if (name) free (name);
134 if (xlfd) free (xlfd);
141 /* Xft silently substitutes fonts if the one you requested wasn't available.
142 This leads to the deplorable situation where we ask for a fixed width font
143 and are given a variable width font instead. This doesn't happen with
144 Courier, since Xft special-cases that one, but it happens with any other
145 fixed width font that is not similarly privileged.
147 Even worse: when Xft substitutes fonts, it makes no attempt to return fonts
148 that are capable of displaying the language of the current locale. For
149 example: if your locale is set to Japanese and you request "Helvetica", Xft
150 silently substitutes "Nimbus Sans" -- a font which is not capable of
151 displaying Japanese characters. If instead you requested "asdfasdfghjkl",
152 you get "Noto Sans CJK JP", which does work. So that's just spectacular.
154 Since there does not appear to be a way to ask Xft whether a particular
155 font exists, we load the font and then check to see if the name we got
156 is the name we requested.
160 - XftFontOpenXlfd is defined as:
161 XftFontOpenPattern (XftFontMatch (XftXlfdParse (xlfd)))
162 - XftFontOpenName is defined as:
163 XftFontOpenPattern (XftFontMatch (XftNameParse (name)))
164 - Calling XftFontOpenPattern with a pattern that has not been filtered
165 through XftFontMatch does not work.
166 - XftFontMatch substitutes another font if the pattern doesn't match.
167 - If the pattern has family "Courier", it substitutes a fixed width
168 font, e.g. "Liberation Mono".
169 - Otherwise it substitutes a variable width font, e.g. "DejaVu Sans".
170 It does this even if the pattern contained "spacing=100" or "110",
171 indicating a monospace or charcell font.
172 - XftXlfdParse does not translate "spacing" from XLFD to XftPattern,
173 but that doesn't matter since XftFontMatch ignores it anyway.
176 https://opensource.apple.com/source/X11ForMacOSXSource/X11ForMacOSXSource-1.0/xc/lib/Xft1/
178 Courier, Helvetica and the other historical PostScript font names seem to
179 be special-cased in /etc/fonts/conf.d/ in the files 30-metric-aliases.conf,
180 45-latin.conf and/or 60-latin.conf, which uses an idiosyncratic scripting
181 language implemented as XML! Some incomplete documentation on that baroque
183 https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
185 The Xft configuration files seem to special-case the names "monospace",
186 "serif" and "sans-serif" as generic fallback fonts.
188 However, "sans-serif" is problematic because it does not survive the trip
189 through XftXlfdParse and XftFontOpenPattern -- XLFD font families cannot
190 include hyphens. So we have no choice but to turn it into "sans serif",
191 which is *not* special-cased by the Xft config files.
193 In summary, everything is terrible, and it's a wonder anything works at all.
196 xlfd_substituted_p (XftFont *f, const char *xlfd)
199 return False; /* No substitutions in the Xft emulator. */
200 # else /* HAVE_XFT */
203 const char *oxlfd = xlfd;
208 if (*xlfd == '-' || strchr (xlfd, '*')) /* it's an xlfd */
210 if (*xlfd != '-') goto FAIL;
212 s = strchr (xlfd, '-'); if (!s) goto FAIL; /* skip foundry */
214 s = strchr (xlfd, '-'); if (!s) goto FAIL; /* skip family */
218 XftPattern *pat = XftXlfdParse (oxlfd, True, True);
219 XftNameUnparse (pat, buf, sizeof(buf)-1);
222 else /* It's an Xft name */
224 s = strchr (xlfd, '-'); if (!s) goto FAIL; /* skip family */
227 xname = strdup (xlfd);
231 XftNameUnparse (f->pattern, fname, sizeof(fname)-1);
232 s = strchr (fname, ':'); /* Strip to "Family-NN" */
234 s = strrchr (fname, '-'); /* Strip to family */
237 ret = !strcasestr (fname, xname);
241 fprintf (stderr, "%s: XFT: requested \"%s\" but got \"%s\"\n",
242 progname, xname, fname);
248 fprintf (stderr, "%s: unparsable XLFD: %s\n", progname, oxlfd);
249 if (xname) free (xname);
251 # endif /* HAVE_XFT */
257 load_font_retry_1 (Display *dpy, int screen, const char *font_list, Bool xft_p)
261 # define LOADFONT(F) (xft_p \
262 ? (void *) XftFontOpenXlfd (dpy, screen, (F)) \
263 : (void *) XLoadQueryFont (dpy, (F)))
264 # define UNLOADFONT(F) (xft_p \
265 ? (void) XftFontClose (dpy, (F)) \
266 : (void) XFreeFont (dpy, (F)))
268 # define LOADFONT(F) ((void *) XLoadQueryFont (dpy, (F)))
269 # define UNLOADFONT(F) XFreeFont (dpy, (F))
281 xft_selftest (dpy, screen);
284 if (! font_list) font_list = "<null>";
286 /* Treat the string as a comma-separated list of font names.
287 Names are XLFDs or the XScreenSaver syntax described above.
288 Try to load each of them in order.
289 If a substitution was made, keep going, unless this is the last.
293 char *token = strdup (font_list);
294 char *otoken = token;
297 while ((name2 = strtok_r (token, ",", &lasts)))
302 /* Strip leading and trailing whitespace */
303 while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n')
306 while (L && (name2[L-1] == ' ' || name2[L-1] == '\t' ||
310 if (font_name) free (font_name);
311 font_name = font_name_to_xlfd (name2);
314 fprintf (stderr, "%s: trying \"%s\" as \"%s\"\n", progname,
318 f = LOADFONT (font_name);
321 /* If we did not get an exact match for the font family we requested,
322 reject this font and try the next one in the list. */
323 if (f && xft_p && xlfd_substituted_p (f, font_name))
326 UNLOADFONT (fallback);
332 fprintf (stderr, "%s: no match for \"%s\"\n", progname, font_name);
334 # endif /* USE_XFT */
339 if (!font_name) abort();
341 /* If the last font in the list was an Xft pattern that matched but
342 was inexact, use it. */
350 if (!font_name) abort();
352 # if !defined(HAVE_XFT) && !defined(HAVE_JWXYZ)
353 /* Xft is now REQUIRED. All of XScreenSaver's resource settings use
354 font names that assume that it is available. However, this lets you
355 limp along without it. But, really, don't do that to yourself. */
356 # define FALLBACK2(F) do { \
358 font_name = (strdup(F)); \
359 f = LOADFONT (font_name); \
360 if (f) fprintf (stderr, "%s: non-XFT fallback \"%s\"\n", \
361 progname, font_name); \
363 if (!f && !strcasestr (font_name, "mono")) {
364 if (!f) FALLBACK2 ("-*-sans serif-medium-r-*-*-*-180-*-*-*-*-*-*");
365 if (!f) FALLBACK2 ("-*-helvetica-medium-r-*-*-*-180-*-*-*-*-*-*");
366 if (!f) FALLBACK2 ("-*-nimbus sans l-medium-r-*-*-*-180-*-*-*-*-*-*");
368 if (!f) FALLBACK2 ("-*-courier-medium-r-*-*-*-180-*-*-*-*-*-*");
369 if (!f) FALLBACK2 ("fixed");
376 fprintf (stderr, "%s:%s loaded %s\n", progname,
377 (xft_p ? " XFT:" : ""), font_name);
378 # if defined(USE_XFT) && defined(HAVE_XFT)
381 XftPattern *p = ((XftFont *) f)->pattern;
383 char *s, *s1, *s2, *s3;
384 XftNameUnparse (p, name, sizeof(name)-1);
385 s = strstr (name, ":style=");
386 s1 = (s ? strstr (s+1, ",") : 0);
387 s2 = (s ? strstr (s+1, ":") : 0);
388 s3 = (s1 && s1 < s2 ? s1 : s2);
389 if (s3) strcpy (s3+1, " [...]");
390 fprintf (stderr, "%s: XFT name: %s\n", progname, name);
392 # endif /* USE_XFT && HAVE_XFT */
395 if (fallback) UNLOADFONT (fallback);
396 if (font_name) free (font_name);
401 #if 1 /* No longer used in XScreenSaver 6.
402 (Used by retired flag, juggle, xsublim) */
404 load_font_retry (Display *dpy, const char *font_list)
406 return (XFontStruct *) load_font_retry_1 (dpy, 0, font_list, 0);
410 #if defined(USE_XFT) || defined(HAVE_JWXYZ)
412 load_xft_font_retry (Display *dpy, int screen, const char *font_list)
414 return (XftFont *) load_font_retry_1 (dpy, screen, font_list, 1);
422 xft_selftest (Display *dpy, int screen)
425 const char *tests[] = {
426 "-*-ocr a std-medium-r-*-*-*-480-*-*-m-*-*-*",
428 "OCR A Std-48:style=Bold Italic",
429 "OCR A Std-48:spacing=100",
430 "OCR A Std-48:spacing=110",
432 "OCR A-48:style=Bold Italic",
433 "OCR A-48:spacing=100",
434 "OCR A-48:spacing=110",
435 "-*-courier-medium-r-*-*-*-480-*-*-m-*-*-*",
436 "-*-courier-bold-o-*-*-*-480-*-*-m-*-*-*",
437 "Courier-48:style=Bold Italic",
438 "Courier-48:style=Italic Bold", /* seems to be illegal */
439 "Courier-48:spacing=100",
440 "Courier-48:spacing=110",
441 "-*-helvetica-bold-o-*-*-*-480-*-*-m-*-*-*",
442 "Helvetica-48:style=Bold Italic",
443 "Liberation Mono-48:style=Bold Italic",
444 "Liberation Sans-48:style=Bold Italic",
445 "-*-sans serif-bold-o-*-*-*-480-*-*-m-*-*-*",
446 "-*-sans-serif-bold-o-*-*-*-480-*-*-m-*-*-*",
447 "-*-sans\\-serif-bold-o-*-*-*-480-*-*-m-*-*-*",
450 const char *tests2[] = { "Helvetica 10",
452 "Helvetica Bold Italic 10",
453 "Helvetica Oblique Bold-10.5",
454 "Times New Roman-10",
455 "Times New Roman Bold-10",
456 "Times New Roman-Bold Oblique Italic 10",
457 "Times New Roman-Oblique Italic Bold 10",
458 "Times-20:style=Bold",
459 "Times-Oblique-20:style=Bold",
460 "sans serif-20:style=Bold",
461 "sans-serif-20:style=Bold",
462 "sans\\-serif-20:style=Bold",
465 fprintf (stderr, "\n");
466 for (i = 0; i < countof(tests2); i++)
467 fprintf (stderr, "%s\n%s\n\n", tests2[i], font_name_to_xlfd (tests2[i]));
469 fprintf (stderr, "\n");
470 for (i = 0; i < countof(tests); i++) {
471 const char *name1 = tests[i];
473 XftPattern *pat1 = 0, *pat2 = 0, *pat3 = 0;
474 char name2[1024], name3[1024];
476 Bool xlfd_p = (*name1 == '-');
478 # define TRUNC(V) do { \
479 char *s = strstr (V, ":style="); \
480 char *s1 = (s ? strstr (s+1, ",") : 0); \
481 char *s2 = (s ? strstr (s+1, ":") : 0); \
482 char *s3 = (s1 && s1 < s2 ? s1 : s2); \
483 if (s3) strcpy (s3+1, " [...]"); \
491 pat1 = XftXlfdParse (name1, True, True);
493 pat1 = XftNameParse (name1);
494 XftNameUnparse (pat1, name2, sizeof(name2)-1);
496 fprintf (stderr, "%s (\"%s\")\n\t-> \"%s\"\n",
497 (xlfd_p ? "XftXlfdParse" : "XftNameParse"),
501 /* Name to pattern to Open */
503 ff = XftFontOpenPattern (dpy, pat1);
506 XftNameUnparse (pat2, name3, sizeof(name3)-1);
509 strcpy (name3, "NULL");
512 fprintf (stderr, "XftFontOpenPattern (\"%s\")\n\t-> \"%s\"\n",
516 /* Name to pattern to Match */
518 pat2 = XftFontMatch (dpy, screen, pat1, &ret);
519 XftNameUnparse (pat2, name3, sizeof(name3)-1);
521 fprintf (stderr, "XftFontMatch (\"%s\")\n\t-> \"%s\", %s\n",
523 (ret == XftResultMatch ? "Match" :
524 ret == XftResultNoMatch ? "NoMatch" :
525 ret == XftResultTypeMismatch ? "TypeMismatch" :
526 ret == XftResultNoId ? "NoId" : "???"));
529 /* Name to pattern to Match to Open */
531 strcpy (name2, name3);
532 ff = XftFontOpenPattern (dpy, pat2);
535 XftNameUnparse (pat3, name3, sizeof(name3)-1);
538 strcpy (name3, "NULL");
541 fprintf (stderr, "XftFontOpenPattern (\"%s\")\n\t-> \"%s\"\n",
548 ? XftFontOpenXlfd (dpy, screen, name1)
549 : XftFontOpenName (dpy, screen, name1));
553 XftNameUnparse (pat1, name2, sizeof(name2)-1);
555 strcpy (name2, "NULL");
558 fprintf (stderr, "%s (\"%s\")\n\t-> \"%s\"\n",
559 (xlfd_p ? "XftFontOpenXlfd" : "XftFontOpenName"),
561 fprintf (stderr, "\n");
566 #endif /* SELFTEST */