midna.dev

Packaging a commercial font with Nix

I recently got a new Framework laptop to serve as my main personal workstation. Before it arrived, I knew that I was going to set it up with NixOS, which I’ve been using more and more on the servers that make up my homelab. This was the first time I was setting up NixOS on a “desktop” computer, though. Much of the setup was a breeze: I was already using home-manager on two MacBooks, so my shell and terminal were already good to go. One thing I didn’t have ready already was fonts.

Nixpkgs has many free fonts already packaged and ready-to-go. Just add the package to fonts.packages in your NixOS config and you’re all set. But for the past several years, my preferred coding font has been Pragmata Pro, which is not free. Because of this, it’s not available in Nixpkgs already.

It’s easy enough to use a font that isn’t packaged. NixOS supports a personal fonts directory just like other Linux distros. When I first set up the machine, I just copied the .ttf files to ~/.local/share/fonts. They worked fine and I called it a day, as there were so many other things to still set up. But part of the reason to use NixOS is to have a single source of truth for how the system is configured. So I did want to eventually figure out how to package up Pragmata Pro so it could be installed with the rest of my config.

For a while, I was pretty stumped about how to do it. Most Nix packages use a fetcher to tell Nix how to get the sources, but there is no publicly-accessible place to fetch them from in this case. To get the font files, I have to log in to my account on the creator’s website, which is not something I could expect Nix to handle. But then I learned about a special fetcher called requireFile.

requireFile is unique in that it doesn’t actually do any fetching. You give the file a name and the hash of its contents, and Nix will expect that file to already be in the store. If it isn’t there, the build will fail with a message explaining that you need to add that file manually. This is exactly what I needed! With this, I can write up a derivation for Pragmata Pro that expects the zip archive I get from the font’s creator to already be available. Then it just needs to extract it and put the font files in the proper locations, like any other Nix font package.

Here’s what that derivation looks like:

{
  lib,
  stdenvNoCC,
  requireFile,
  unzip,
}:
stdenvNoCC.mkDerivation rec {
  pname = "pragmata-pro";
  version = "0.829";

  src = requireFile {
    name = "PragmataPro0.829-ptikme.zip";
    url = "https://fsd.it/shop/fonts/pragmatapro/";
    hash = "sha256-/DgsOMHi/bAE55SDgf5f59q81yvuVERSn/K5Y+D3Pyw=";
  };

  nativeBuildInputs = [unzip];

  sourceRoot = "PragmataPro${version}";

  installPhase = ''
    runHook preInstall

    install -Dm644 */*.otf -t $out/share/fonts/opentype
    install -Dm644 *.ttf -t $out/share/fonts/truetype

    runHook postInstall
  '';

  meta = with lib; {
    description = "A condensed monospaced font optimized for screen";
    homepage = "https://fsd.it/shop/fonts/pragmatapro/";
    license = licenses.unfree;
    maintainers = [];
    platforms = platforms.all;
  };
}

There’s nothing too complicated going on here. The src attribute of the derivation uses requireFile to specify the zip archive that should already be present in the Nix store when this package is built. Because the src is a zip archive, I need to include unzip in the nativeBuildInputs so it’s available to extract it. Otherwise, extracting the archive is handled automatically by Nixpkgs.

The archive unfortunately includes a useless extra __MACOSX directory at the top-level, and the automatic unpacking in Nixpkgs expects a single directory. To solve this, I include the sourceRoot attribute to tell it which directory has the actual sources. In this case, there is a single subdirectory named PragmataPro0.829 that contains everything, so that’s what I use. If the font files had been at the root of the archive or in multiple subdirectories, I could have used . instead.

The last step is just making sure the font files make it to the correct locations in the derivations output. I use the install command to place them in $out/share/fonts/opentype and $out/share/fonts/truetype. The TrueType versions are in the sourceRoot directory, while the OpenType versions are in a subdirectory of that, so that’s why they have slightly different glob patterns.

I can add this package as an output in my nix-config flake, and then go ahead and try to build it.

error: builder for '/nix/store/hh594radyax5sq5684jdh166mlsl2zys-PragmataPro0.829-ptikme.zip.drv' failed with exit code 1;
       last 10 log lines:
       > ***
       > Unfortunately, we cannot download file PragmataPro0.829-ptikme.zip automatically.
       > Please go to https://fsd.it/shop/fonts/pragmatapro/ to download it yourself, and add it to the Nix store
       > using either
       >   nix-store --add-fixed sha256 PragmataPro0.829-ptikme.zip
       > or
       >   nix-prefetch-url --type sha256 file:///path/to/PragmataPro0.829-ptikme.zip
       >
       > ***
       >
       For full logs, run 'nix log /nix/store/hh594radyax5sq5684jdh166mlsl2zys-PragmataPro0.829-ptikme.zip.drv'.
error: 1 dependencies of derivation '/nix/store/vbfpjqb8dz2y0xpngpwl5z56mp027gs2-pragmata-pro-0.829.drv' failed to build

Whoops! I forgot to add the font archive to the store. I can just go download that real quick and run the command it gave me to get it added, and then run the build again.

$ nix-store --add-fixed sha256 PragmataPro0.829-ptikme.zip                     
/nix/store/gdj9ijgzqxwijqkzx67nqhicg784q4q7-PragmataPro0.829-ptikme.zip
$ nix build .#pragmata-pro
$ ls result/share/fonts/truetype 
PragmataProB_0829.ttf
PragmataProB_liga_0829.ttf
PragmataProI_0829.ttf
PragmataProI_liga_0829.ttf
PragmataPro_Mono_B_0829.ttf
PragmataPro_Mono_B_liga_0829.ttf
PragmataPro_Mono_I_0829.ttf
PragmataPro_Mono_I_liga_0829.ttf
PragmataPro_Mono_R_0829.ttf
PragmataPro_Mono_R_liga_0829.ttf
PragmataPro_Mono_Z_0829.ttf
PragmataPro_Mono_Z_liga_0829.ttf
PragmataProR_0829.ttf
PragmataProR_liga_0829.ttf
PragmataProZ_0829.ttf
PragmataProZ_liga_0829.ttf

It works! Now I can include this package in fonts.packages in my NixOS config, and it will be installed as part of the system.

You may wonder what the point of this is if I still have to manually download this font when I set up my system. And you’re right, it’s not as convenient as it would be if this font were freely available. But I do think there’s still a benefit of including the font in my config through this package. Other parts of my config refer to this font. That created a dependency on a manual step that could easily be forgotten. If I set up this machine from scratch, parts of my system would not be working 100% correctly until I remembered to go download and install this font.

Now there’s still a manual step that may need to be done, but if it does, Nix will prompt me when I go to build the system. It will tell me exactly what I need to do to get things working as expected, and that gives me some comfort.