typst-nix/lib/default.nix
2026-10-21 15:20:54 +01:00

192 lines
5.1 KiB
Nix

{
lib,
config,
hasSystem,
pkgs,
flakeInputs,
...
}:
let
compileTypst =
{
pkgs,
# package to take the typst binary from
typstPackage ? pkgs.typst,
# possible values: pdf, png, svg, html, bundle
format ? "pdf",
# root of the typst project
rootDir,
# main typst file
entrypoint ? "${rootDir}/main.typ",
# lockfile, representing the dependencies
lockfile ? "${rootDir}/typst.lock",
# exposed in Typst as `sys.inputs`. must be an attrset of strings
inputs ? { },
# exposed in Typst as `sys.inputs`. must be an attrset of paths
inputFiles ? { },
# paths to fonts
fontPaths ? [ ],
# if set, Typst's own embedded fonts will be ignored
ignoreEmbeddedFonts ? false,
# UNIX timestamp exposed by datetime functions
creationTimestamp ? 0,
# pretty-format the output
pretty ? false,
pages ? null,
# see typst c --help
noPdfTags ? false,
# see typst c --help
ppi ? 144,
features ? [ ],
...
}:
let
typstPackage' = if builtins.isFunction typstPackage then typstPackage pkgs else typstPackage;
inputArgs = lib.flatten (
lib.mapAttrsToList (key: value: [
"--input"
"${key}=${builtins.toJSON value}"
]) inputs
);
inputFileArgs = lib.mapAttrsToList (
key: path:
" --input " + lib.escapeShellArg key + "=\"$(cat " + lib.escapeShellArg (toString path) + ")\""
) inputFiles;
args = [
"typst"
"compile"
"--format"
format
"--root"
rootDir
"--ignore-system-fonts"
"--package-path"
"/dev/null"
"--package-cache-path"
".cache/typst"
"--creation-timestamp"
(builtins.toString creationTimestamp)
"--ppi"
(builtins.toString ppi)
]
++ inputArgs
++ (
if (builtins.length fontPaths) > 0 then
[
"--font-path"
(lib.concatStringsSep ":" fontPaths)
]
else
[ ]
)
++ (if ignoreEmbeddedFonts then [ "--ignore-embedded-fonts" ] else [ ])
++ (if pretty then [ "--pretty" ] else [ ])
++ (
if (pages != null) then
[
"--pages"
pages
]
else
[ ]
)
++ (if noPdfTags then [ "--no-pdf-tags" ] else [ ])
++ (
if (builtins.length features) > 0 then
[
"--features"
(lib.concatStringsSep "," features)
]
else
[ ]
)
++ [ entrypoint ];
ext = if format == "bundle" then "" else ".${format}";
dependencies = builtins.fromJSON (builtins.readFile lockfile);
parseSpec =
spec:
let
parts = builtins.match "@([^/]+)/([^:]+):(.+)" spec;
in
assert lib.assertMsg (parts != null) "${spec} is not a valid package spec";
{
namespace = builtins.elemAt parts 0;
name = builtins.elemAt parts 1;
version = builtins.elemAt parts 2;
};
previewPackage =
{
name,
version,
hash,
}:
builtins.fetchTarball {
url = "https://packages.typst.org/preview/${name}-${version}.tar.gz";
sha256 = hash;
};
linkPackage =
entry:
let
spec = parseSpec entry.spec;
storeDir = previewPackage {
name = spec.name;
version = spec.version;
hash = entry.hash;
};
in
assert lib.assertMsg (
spec.namespace == "preview"
) "only packages from @preview are supported at the moment";
''
mkdir -p .cache/typst/preview/${spec.name}
ln -s ${storeDir} .cache/typst/preview/${spec.name}/${spec.version}
'';
linkDeps = lib.concatStringsSep "\n" (map linkPackage dependencies.packages);
in
assert lib.assertMsg (builtins.isAttrs inputs) "`inputs` must be an attrset";
assert lib.assertMsg (builtins.isAttrs inputFiles) "`inputFiles` must be an attrset";
pkgs.stdenv.mkDerivation {
name = "out${ext}";
src = rootDir;
buildInputs = with pkgs; [
coreutils
typstPackage'
];
buildPhase = ''
#!/bin/bash
set -euo pipefail
TMP="$(mktemp -d)"
cd "$TMP"
export HOME="$TMP"
${linkDeps}
${lib.escapeShellArgs args}${lib.concatStringsSep " " inputFileArgs} - >$out
'';
};
compileTypstSys = args: compileTypst ({ inherit pkgs; } // args);
naersk = flakeInputs.naersk;
naersk' = pkgs.callPackage naersk { };
typstLock = naersk'.buildPackage {
src = ./..;
buildInputs = [ pkgs.gcc ];
};
in
lib.recursiveUpdate
{
config.lib.compileTypst = if hasSystem then compileTypstSys else compileTypst;
}
(
lib.optionalAttrs hasSystem {
config.checks.testSimple = compileTypstSys {
rootDir = ../tests/simple;
};
config.packages.default = typstLock;
config.apps.default = {
program = "${typstLock}/bin/typst-lock";
};
}
)