Skip to content

Published

Towards Deterministic Typst Compilation

Cover Image for Towards Deterministic Typst Compilation

Typst is really nice. Being able to compose a PDF in a markup language that doesn't enforce tight coupling between the style and content (like Word or Google Docs do) enables me to fearlessly edit my PDF documents without the mental overload of constantly switching back and forth between adding content and manually fixing the style of the content I added.

And yet, in terms of dependency management, there's still something to be desired. Some features, such as fonts, images, and data loading, depend on external files being available on your system. This isn't something handled automatically as is the case with tools such as npm: as a Typst user, you have to manage the dependencies yourself. And while keeping the necessary files co-located with your Typst source files is always an option, this prevents more advanced usage such as keeping your project in sync with an upstream dependency that updates periodically, whether it's a font, an icon library, or critical data.

There is also the problem of reproducibility: when we run typst compile, the result can be different depending on the machine on which it runs. In other words, its output depends on external state. What we need is a way to provide a hermetically sealed environment that, given a list of dependencies, runs typst compile to always produce the correct result.

Luckily, a solution to both of these issues already exists in the form of Nix.

The Nix Package Manager

Nix is a purely functional package manager. This means packages are built in a manner analogous to how pure functions work: producing the desired output from nothing more than a set of inputs you describe. The beauty of this approach is in the reliability it enables given a well-crafted specification written in the Nix language.

Nix flakes enable a user experience more akin to that of npm or Cargo compared to the base Nix experience:

However, actually using Nix dependencies ("flake inputs") in a Typst project is non-trivial. You can install them to the Nix store with nix flake archive, but without using derivations you'd have to reference the dependencies with absolute paths, which is hardly reproducible or user-friendly.

Typix

Typix solves the issue of making Nix dependencies available in Typst projects. Here's what it looks like:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    typix.url = "github:loqusion/typix";
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };
  outputs = {
    nixpkgs,
    typix,
    font-awesome,
    ...
  }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    typixLib = typix.lib.${system};
  in {
    packages.${system}.default = typixLib.buildTypstProject {
      src = typixLib.cleanTypstSource ./.;
      typstSource = "main.typ";
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Let's break it down a bit:

  • typixLib.buildTypstProject — Produces a derivation which runs typst compile in a sandboxed environment where dependencies are automatically made available from the attributes you provide, namely fontPaths and virtualPaths (and technically src).
  • src — Represents a subset of the working directory provided as input to typst compile.
    • typixLib.cleanTypstSource — Filters out all files not tracked by version control, as well as all files besides Typst source files (*.typ).
  • typstSource — Specifies a relative path to the entrypoint of your Typst project.
  • fontPaths — List of paths to fonts.
  • virtualPaths — List of paths; each src is made virtually available at dest.

Running nix build will produce a PDF whose symlink is available at ./result. However, more often than not you'll want the actual PDF file: buildTypstProjectLocal is a convenience wrapper built for that purpose.

Typix also has a derivation builder for watching the input file and recompiling on changes (via typst watch): watchTypstProject. Using this in tandem with a PDF viewer that updates in real time (such as Zathura) is an insane quality of life enhancement comparable with the official Typst web app.

More information can be found on GitHub.

Limitations

Typst packages are currently unsupported. This is because the Typst package manager requires an internet connection during the invocation of typst compile, which is unavailable in the Nix sandbox. Support may be added in the future, but for now there is a guide for getting them to work today.


  • Nix
  • Typst