I 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.