How to use the Fontconfig Library with C
Apr 3, 2020 #floss #programming #fontsI have been writing a PostScript interpreter during my self-isolation for SARS-CoV-2. Since Sir Issac Newton had his annus mirabilis during isolation from the black plague, I figured that I should do my own little project during my state-mandated time at home.
But this post isn’t about writing an interpreter. It’s about a very special problem that you have when writing a PostScript interpreter: fonts. A PostScript interpreter needs to have access to fonts, whether those be bundled with the interpreter itself or available on the system beforehand. So, instead of reinventing the wheel like I have with other projects, I decided to go ahead and use a standard that someone else has made. The standard that I decided to go with, instead of hard-coding font selection into my program, is Fontconfig.
Background
Fontconfig is a library that allows users to install, manage, and select their desired fonts. Most people will not need to go beyond the defaults used with their distribution (e.g. that bundled with Ubuntu or Fedora). The user documentation exists and isn’t that bad. Plus, there are a plethora of existing tutorials and guides on how to setup fontconfig on the user-side.
My issue was that the developer (or library) side of the documentation is terrible. There are no code snippets and the background information is scant at best. In order to use the library I had to dive into the bundled source code for fc-match and adapt it to my needs.
Fun fact: as of time of writing there is a uninitialized pointer de-reference in
fontconfig’s fc-match.c
. Can you find it? Now, let’s get to the meat of this
post…
How to use the Fontconfig Library with C
Let’s figure out how to find a font (or substitute) for a given style name. In this example, we’re going to use “Helvetica Bold Italic‟ as desired font. How do we find the location on disk of a suitable font to represent Helvetica Bold Italic?
First we start by including the necessary libraries and accepting arguments from the command line:
#include <stdio.h>
#include <stdlib.h>
#include <fontconfig/fontconfig.h>
int main(int argc, char **argv) {
FcConfig* conf;
FcFontSet* fs;
FcObjectSet* os = 0;
FcPattern* pat;
FcResult result;
if (argc < 2) {
fprintf(stderr, "usage: \n");
fprintf(stderr, " ./fc-match \"Font Name\"\n");
return 1;
}
conf = FcInitLoadConfigAndFonts();
pat = FcNameParse((FcChar8*) argv[1]);
if (!pat)
return 2;
Here, conf
will hold our fontconfig-defined user configuration. We aren’t
going to mess with it, but we will read from it. pat
will to hold the parsed
fontconfig query that the user will provide us, such as
helvetica:bold:italic
. Let’s continue:
FcConfigSubstitute(conf, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
fs = FcFontSetCreate();
os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_FILE, (char*)0);
FcFontSet *font_patterns;
font_patterns = FcFontSort(conf, pat, FcTrue, 0, &result);
if (!font_patterns || font_patterns->nfont == 0) {
fprintf(stderr, "Fontconfig could not find ANY fonts on the system?\n");
return 3;
}
In the listing above we perform some basic configuration-defined substitutions
for our query. But really, this is just setting us up to find the
patterns associated with our query string from earlier. Note that here we are
building an FcObjectSet
with the FC_FAMILY
, FC_STYLE
, and FC_FILE
attributes. That FcObjectSet
tells fontconfig we want to know about a
matching font’s family, style, and filepath.
We aren’t going to use anything here besides FC_FILE
, but it’s nice to show
we can find use the metadata associated with a font. You can find a full list
of available font properties
here.
Now we will call FcFontSort
which generates a list of fonts matching our query
from best-to-worst, but we are only interested in the best match.
After that, we’re going to double-check that our query returned a
match and prepare that best match to be loaded from storage.
Oh, and we also do some cleanup of the resources we used earlier.
FcPattern *font_pattern;
font_pattern = FcFontRenderPrepare(conf, pat, font_patterns->fonts[0]);
if (font_pattern){
FcFontSetAdd(fs, font_pattern);
} else {
fprintf(stderr, "Could not prepare matched font for loading.\n");
return 4;
}
FcFontSetSortDestroy(font_patterns);
FcPatternDestroy(pat);
Finally, we need to retrieve the filepath of the best matching candidate font.
To do this we look into the FcFontSet
that we made earlier. We are going to
filter that FcFontSet
to only tell us
about the properties we want (i.e. FC_FILE
et al from earlier).
After we filter out those unnecessary properties, we query for the value of the
FC_FILE
property of our FcPattern
using FcPatternGet
. This will tell us
the location of the file matching the user’s font query. And of course, we
do some cleanup:
if (fs) {
if (fs->nfont > 0) {
FcValue v;
FcPattern *font;
font = FcPatternFilter(fs->fonts[0], os);
FcPatternGet(font, FC_FILE, 0, &v);
const char* filepath = (char*)v.u.f;
printf("path: %s\n", filepath);
FcPatternDestroy(font);
}
FcFontSetDestroy(fs);
} else {
fprintf(stderr, "could not obtain fs\n");
}
Once we have the path of the font we wanted, we can load it with FreeType or a similar library. That’s left as an exercise to the reader.
Now we just have one more bit cleanup our mess and it’s time to return from
main()
:
if (os)
FcObjectSetDestroy(os);
return 0;
}
And that’s it! In case you have issues using/building this code all of the code snippets plus a working Makefile are located on Gitlab.