Site Logo

Delyo's blog

A specimen of all installed fonts using bash

— published on 04·09·25

The general tools for font management that Linux distributions ship don't always cut it. GNOME's FontViewer is... sexy and stylish, but its 2-letter preview in the fonts list (see screenshot in link) doesn't really allow a comprehensive feel of the font at a glance. If you're looking for the right fit, odds are you'll be stuck in a back and forth of clicks between the font list and the individual font pages, which show a waterfall. If you're looking for info and for the properties of the font, that'll cost you three to four clicks each time (plus the animation time). As for Font Manager, it's the first thing I suggest that anyone working with fonts installs. It's free and open, and it does the job well to disable fonts without removing them, classify your collection and add directories, and generally get to the oft-needed technical information fast enough. If you want a quick preview of all the fonts though, you'll have to go to the Browse drawer, from which you can see all the variants, but can't jump to their respective information pages. From the Manage drawer on the other hand, you need to open a dropdown for every font family (or typeface, yes) whose variants you want to see the info on. It's sexy, it's functional, and you still really need it very often.

But as a personal quirk, I want to have a quick and accessible way to see all the font variants, and possibly add more content later on: font info, style, etc. I must credit a certain StackOverflow user for the idea on how to do it. I've lost this thread since, and can't manage to find it, so I'm on my own. Here's what I want: a document that lists every font that is enabled on my Linux computer, and displays the name, rendered with the font. Every variant, from Condensed to Extra Bold to Extended should be listed, and everything should be visible at a glance by scrolling though the list without waiting. Why do I want this? It makes choosing fonts so much simpler. If I manage to do this, I might add more options to the page, to make the font browsing even better.

Before installing and managing a full-on font library software like the one Luuse made for Le 75, I wanted to see if a simpler way is possible. And it is.

Enter fontconfig

Fontconfig is generally how most Linux distributions, and a great share of their programs, handle fonts and their files. You can see a [user manual for fontconfig here]. Without going in too deep, fontconfig allows us to do two invaluable things to debug font-related issues on Linux. We can list and filter fonts and their properties using fc-list (fc-list manual) and we can reload our font configuration with fc-cache. A third command called fc-match allows to look for suitable fonts by searching for specific properties, like sans, serif, or weight. It would be handy in moments where we're blind-designing, a.k.a designing through code without seeing the output almost immediately, but it's not much use in the current use case.

fc-list is powerful and fast; AFAIK this is what software made with the Gnome Toolkit uses. It outputs a plain-text line for each font, with separators between the different pieces of data called properties (see Font Properties section in linked fontconfig manual). We can filter which properties we want to be shown. Here's an example where we want to display only the font family, its slant (roman/italic/oblique) and its weight:

$ fc-list : family weight slant
### The colon means "look in all the fonts".

Linux Libertine:slant=100:weight=80
Work Sans,Work Sans Light:slant=0:weight=50
Noto Serif Display,Noto Serif Display Thin:slant=100:weight=0
Enby Gertrude,Enby\-Gertrude:slant=0:weight=100
nanook:slant=0:weight=200
Roboto,Roboto Black:slant=0:weight=210
EB Garamond Initials:slant=0:weight=80
Open Sans,Open Sans Condensed ExtraBold:slant=0:weight=205
[...]

In the output, we first have a font family name. Depending on how the font is configured, after a comma, we have (in some cases) a font's more explicit name. After a first colon its slant (0 is roman, 100 is italic, 110 is oblique, don't ask me why), and after a second colon its weight.

You might have noticed the font weight is in numbers instead of words. It's easier to interpret the "steps" of weight this way, instead of relying on naming, because naming conventions vary. Ultra Bold is in many cases exactly the same concept as Extra Bold, but a computer can't interpret this, so numbers are better suited. And if you're a Web designer, you'll notice this font weight has nothing to do with the CSS font width convention where 400 is Regular and 700 is bold, with unnamed steps going from 100 to 900 (Ultra Thin to Black). Truth is I have no idea why fc-list shows those weights. If you're aware why, please send me a message.

The good news is that fc-list follows a logic for weights, even if it's curious. We can 'translate' the values from fc-list to CSS-type weight and slant as such:

fc weight CSS Weight
0 100 Ultra Thin
45 or 47.5 200 Thin
50 300 Light
80 or 90 400 Regular
100 500 Medium
180 600 Semi Bold
200 700 Bold
205 800 Extra Bold
210 900 Black

And a slant table:

fc slant CSS Slant
0 normal Roman
100 italic Italic
110 oblique Oblique

This takes some testing as some fonts have very weird values, but it works for most outputs.

We now have a way to show all our fonts and their variants with the properties we care about. But as you can see from the code excerpt, they'e not alphabetically sorted. Let's fix that: fc-list : family weight slant | sort:

Cantarell:slant=0:weight=80
Chemins:slant=0:weight=80
CMU Typewriter Text:slant=0:weight=100
CMU Typewriter Text:slant=0:weight=200
CMU Typewriter Text:slant=100:weight=100
CMU Typewriter Text:slant=100:weight=200
D050000L:slant=0:weight=80
DejaVu Math TeX Gyre:slant=0:weight=80
DejaVu Sans,DejaVu Sans Condensed:slant=0:weight=200

Transforming the text

You might have noticed the vertical bar there called a pipe |. With that pipe, we can chain commands one after another without having to create variables or store information in files for now. sort takes the output of fc-list, but we can also "pipe" the output of sortonto another program. Before choosing this third program, let's think a bit about what we want to do. We have a line for every font on our computer. From this, we want to create one HTML element per font, typeset in the respective font with its weight and slant. Because we want our page to be readable by ourselves, we should include text in that HTML element, this text being the font's name. So what we want to do is go from this:

CMU Typewriter Text:slant=100:weight=200

to this:

<p style="font-family:'CMU Typewriter Text'; font-style:'italic'; font-weight: 400"> CMU Typewriter Text </p>

It's easier to do inline styling of each element instead of creating a separate CSS section or file where we double the work. This latter approach may be cleaner, but we're looking to make a font specimen, not a self-landing rocket.

What this means until now is that we need to keep some parts of the information as is, and replace the rest. This is called a substitution, or "search and replace", and a handy thing called sed, a stream editor can do just that: search for a pattern, and replace it with another pattern. The patterns themselves are built following regular expressions, a dark magic discipline come from Mordor itself, yet extremely useful to search for text without exactly matching it. Since all our fonts have different names, we're going to have to match their pattern using the surrounding text. Notice how there's a colon between each property on every line. In some cases the font name is composed of both the family name and the individual font name. We need a way to capture those separated groups. Good news...

Capture groups

So-called capturee groups are regular expressions' way to mark the important parts we want to keep, structurally similar to this: (grp1),(grp2):(grp3):(grp4). Then we can reintroduce these groups in the replace part later. Let's first isolate the font name, the slant, and the weight. We'll worry about the rest later. This part is more complicated so I advise you to look into regular expressions, maybe play around with Regex 101.

/(.*):slant=([0-9]*):weight=([0-9]*)$/

In the first parentheses, we match any character zero or more times until a : colon is read. In the second parentheses, we really only care about the numbers. This is why we match the :slant= part to make sure we get the text right after it, but don't include it. We match any digit zero or more times. The same logic applies to the third parentheses, before mathing the end of line with $.

Thus for the line DejaVu Sans,DejaVu Sans Condensed:slant=0:weight=200 for example, the first group will be DejaVu Sans,DejaVu Sans Condensed, the second will be 0 and the third will be 200.

As it stands, the first group is a little confusing since we have two possible font names. One is the family name, the other is the name of the Condensed variant, which is more precise. It's safer to use the latter since it already gives us info on the width of the font. Here's what we'll do: if there's no comma and thus only one font name, use it. If there's a comma and thus two font names, only keep the one after the comma. Keep DejaVu Sans Condensed and throw away DejaVu Sans,. Regex logic for this is achievable but hard to find out:

/^(.*,)?(.*)/

We first match the beginning of the line, to be safe. Then we have a first group consisting of any character any number of times followed by a comma. Since we don't have commas and two names in some of the lines, we add ? to make that entire group optional. If there is such a group, it must be in the beginning and it must contain a comma. Then the second group, the one we really need, is any character any number of times (since font names vary a lot). Let's put that in our entire regular expression, replacing the first parentheses.

/^(.*,)?(.*):slant=([0-9]*):weight=([0-9]*)$/

We know how to capture the useful information, but we haven't used it while replacing the rest yet. Here sed comes into play. Used with the -E flag, sed can take in a regular expression and its replacement: sed -E 's/pattern/replacement/g' (s is for substitute x with y, g is for global meaning don't stop after the first match). We know our pattern but what about our replacement? It's whatever we want it to be, and we use \1 \2 \3 etc. to "copy paste" the groups we captured. Since there are a lot of special characters here I'll be careful and escape them using \ so that the program knows they're text and not special commands. Our replacement should be:

/\<p style=\"font-family:`\2`\; font-weight:\4\; font-style:\3\;\"\> \2 \<\/p\>/

If we put the pattern and the replacement together inside the command:

sed -E 's/^(.*,)?(.*):slant=([0-9]*):weight=([0-9]*)$/\<p style=\"font-family:`\2`\; font-weight:\4\; font-style:\3\;\"\> \2 \<\/p\>/g'

We get:

<p style="font-family:`DejaVu Sans Condensed`; font-weight:200; font-style:0;"> DejaVu Sans Condensed </p>

This gets us closer, but we're not quite there. Notice how the font-family value is between backticks. I did this because I can't nest an infinite amount of single and double quotes in the expression that sed takes. Backticks are special enough to be unique in the mix so we can find and replace them later. Also, the font-weight and font-style have values that mean something to fontconfig but not to CSS.

We have to translate this information.

Translating for CSS

Remember the two tables in the beginning of the post? They'll be of use here. Now that we have the structure of our line-per-line search and replace, we'll move on to changing the values to something that HTML and CSS can understand.

But first let's write our entire line, from listing the fonts, through sorting them, to search and replace. Use pipes to chain the commands together and add a pipe at the end after which we'll write our new commands:

fc-list : family slant weight | sort | sed -E 's/^(.*,)?(.*):slant=([0-9]*):weight=([0-9]*)$/\<p style=\"font-family:`\2`\; font-weight:\4\; font-style:\3\;\"\> \2 \<\/p\>/g' |

The next command will also be a sed expression with a replacement, but this time we'll be replacing the fontconfig slant values with their respective CSS counterparts. To avoid calling sed multiple times we'll chain our expressions:

sed -E 's/pattern1/replacement1/g;s/pattern2/replacement2/g;s/pattern3/replacement3/g'

We want to replace the nubers with correct values, right? To be on the safe side, let's make sure we're replacing the numbers we mean to replace, and not some random number that could be in the font's name. First we replace font-style so we search for font-style:0 or font-style:100 or font-style:110. We replace them respectively with normal, italic, oblique.

sed -E 's/font-style:100/font-style:italic/g;s/font-style:110/font-style:oblique/g;s/font-style:0/font-style:normal/g'

Add a pipe | after that and let's fix those backticks into single quotes. To do this, put double quotes this time around the expression. Essentially, take whatever is between backticks, keep it and change the backticks to single quotes. Make sure backticks and single quotes are escaped since they're special characters.

sed -E "s/\`(.*)\`/\'\1\'/g

Add another pipe after that and let's move on to our font weight. By now you should get the gist, so here's the code:

sed -E "s/weight:0/weight:100/g;s/weight:45|weight:47.5/weight:200/g;s/weight:50/weight:300/g;s/weight:80|weight:90/weight:400/g;s/weight:100/weight:500/g;s/weight:180/weight:600/g;s/weight:200/weight:700/g;s/weight:205/weight:800/g;s/weight:210/weight:900/g"

The | inside the expression aren't pipes, they're OR statements (look at the tables above).

Here we are, we've written all of the necessary parts of our code. Chaining them together and pasting the whole thing in a Bash terminal (not sure this is POSIX compliant for those who care), we can come out with HTML that will show us one line per font, with the font's name written in the font itself, for each font enabled on our Linux machine. All that's left is to put that HTML inside a file called fontlist.html.

But we can't just stop here so let's do two more tiny things. First, seeing the font's name does allow us to see more characters than Gnome's FontViewer. But by just adding contenteditable="true" inside the paragraph element's attributes, most browsers will make it possible to edit the text and write whichever characters we want to see. Second, we're not barbarians, and a little style never hurt anybody. let's end our command with ; and add a style element filled with whatever we want using echo "<style>whatever</style>" >> fontlist.html.

And here's how we created a personal font library/preview/specimen generator in just about 700 characters. From here on, you're free to do anything.

fc-list : family weight slant |
    sort | 
    sed -E 's/^(.*,)?(.*):slant=([0-9]*):weight=([0-9]*)$/\<p contenteditable=\"true\" spellcheck=\"false\" style=\"font-family:`\2`\;font-weight:\4\;font-style:\3\;\"\>\2\<\/p\>/g' | 
    sed -E 's/font-style:100/font-style:italic/g;s/font-style:110/font-style:oblique/g;s/font-style:0/font-style:normal/g' |
    sed -E "s/\`(.*)\`/\'\1\'/g;s/weight:0/weight:100/g;s/weight:45|weight:47.5/weight:200/g;s/weight:50/weight:300/g;s/weight:80|weight:90/weight:400/g;s/weight:100/weight:500/g;s/weight:180/weight:600/g;s/weight:200/weight:700/g;s/weight:205/weight:800/g;s/weight:210/weight:900/g" >| fontlist.html;
    echo "<style>body{font-size:2rem; background: #EAEAEA; color: #333333; padding: 2ch;}</style>" >> fontlist.html

Here's how it looks, listing all font variants:

Screenshot of font library Screenshot of font library

Written by a human, not by AI

Feedback

If you have any thoughts or comments about this site or about an article, send me an email!

If you like what you just read,

Buy Me A Coffee