# This module is heavily inspired by the corresponding NixOS module. See # # https://github.com/NixOS/nixpkgs/blob/23.11/nixos/modules/config/fonts/fontconfig.nix { config, lib, pkgs, ... }: let cfg = config.fonts.fontconfig; profileDirectory = config.home.profileDirectory; fontConfigFileType = lib.types.submodule ( { name, ... }: { options = { enable = lib.mkEnableOption "Whether this font config file should be generated."; text = lib.mkOption { type = lib.types.nullOr lib.types.lines; default = null; description = "Verbatim contents of the config file. If this option is null then the 'source' option must be set."; }; source = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Config file to source. Alternatively, use the 'text' option instead."; }; label = lib.mkOption { type = lib.types.str; default = name; defaultText = ""; description = "Label to use for the name of the config file."; }; priority = lib.mkOption { type = lib.types.ints.between 0 99; default = 90; description = '' Determines the order in which configs are loaded. Must be a value within the range of 0-99, where priority 0 is the highest priority and 99 is the lowest. ''; }; }; } ); in { meta.maintainers = with lib.maintainers; [ bmrips rycee ]; imports = [ (lib.mkRenamedOptionModule [ "fonts" "fontconfig" "enableProfileFonts" ] [ "fonts" "fontconfig" "enable" ] ) ]; options = { fonts.fontconfig = { enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Whether to enable fontconfig configuration. This will, for example, allow fontconfig to discover fonts and configurations installed through {var}`home.packages` and {command}`nix-env`. ''; }; defaultFonts = { monospace = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Per-user default monospace font(s). Multiple fonts may be listed in case multiple languages must be supported. ''; }; sansSerif = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Per-user default sans serif font(s). Multiple fonts may be listed in case multiple languages must be supported. ''; }; serif = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Per-user default serif font(s). Multiple fonts may be listed in case multiple languages must be supported. ''; }; emoji = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Per-user default emoji font(s). Multiple fonts may be listed in case a font does not support all emoji. Note that fontconfig matches color emoji fonts preferentially, so if you want to use a black and white font while having a color font installed (eg. Noto Color Emoji installed alongside Noto Emoji), fontconfig will still choose the color font even when it is later in the list. ''; }; }; antialiasing = lib.mkOption { type = with lib.types; nullOr bool; default = null; description = "Whether to enable font antialiasing."; example = true; }; hinting = lib.mkOption { type = with lib.types; nullOr (enum [ "none" "slight" "medium" "full" ]); default = null; description = "The font hinting mode."; example = "slight"; }; subpixelRendering = lib.mkOption { type = with lib.types; nullOr (enum [ "none" "rgb" "bgr" "vertical-rgb" "vertical-bgr" ]); default = null; description = "The sub-pixel rendering mode."; example = "rgb"; }; configFile = lib.mkOption { type = lib.types.attrsOf fontConfigFileType; default = { }; description = '' Extra font config files that will be added to `~/.config/fontconfig/conf.d/`. Files are named like `fontconfig/conf.d/{priority}-{label}.conf`. ''; example = { tamzen = { enable = true; label = "tamzen-disable-antialiasing"; text = '' Disable anti-aliasing for Tamzen since it is a bitmap font Tamzen false ''; priority = 90; }; # => conf.d/90-tamzen-disable-antialiasing.conf commit-mono-options = { enable = true; source = "./resources/fontconfig/commit-mono.conf"; priority = 80; }; # => conf.d/80-commit-mono-options.conf }; }; }; }; config = lib.mkIf cfg.enable { home.packages = [ # Make sure that buildEnv creates a real directory path so that we avoid # trying to write to a read-only location. (pkgs.runCommandLocal "dummy-fc-dir1" { } "mkdir -p $out/lib/fontconfig") (pkgs.runCommandLocal "dummy-fc-dir2" { } "mkdir -p $out/lib/fontconfig") ]; home.extraProfileCommands = '' if [[ -d $out/lib/X11/fonts || -d $out/share/fonts ]]; then export FONTCONFIG_FILE="$(pwd)/fonts.conf" cat > $FONTCONFIG_FILE << EOF $out/lib/X11/fonts $out/share/fonts $out/lib/fontconfig/cache EOF ${lib.getBin pkgs.fontconfig}/bin/fc-cache -f rm -f $out/lib/fontconfig/cache/CACHEDIR.TAG rmdir --ignore-fail-on-non-empty -p $out/lib/fontconfig/cache rm "$FONTCONFIG_FILE" unset FONTCONFIG_FILE fi # Remove the fontconfig directory if no files were available. if [[ -d $out/lib/fontconfig ]] ; then rmdir --ignore-fail-on-non-empty -p $out/lib/fontconfig fi ''; fonts.fontconfig.configFile = let mkFontconfigConf = conf: '' ${conf} ''; in { fonts = { enable = true; priority = 10; source = null; # Set the source as null explicitly so that it cannot be overwritten by mistake by a user text = mkFontconfigConf '' Add fonts in the Nix user profile ${config.home.path}/etc/fonts/conf.d ${config.home.path}/etc/fonts/fonts.conf ${config.home.path}/lib/X11/fonts ${config.home.path}/share/fonts ${profileDirectory}/lib/X11/fonts ${profileDirectory}/share/fonts ${config.home.path}/lib/fontconfig/cache ''; }; rendering = let set = name: value: let xmlValue = if builtins.isBool value then "${lib.boolToString value}" else if builtins.isString value then "${value}" else throw ("expected bool or string but got ${builtins.typeOf value}: ${toString value}"); in '' ${xmlValue} ''; content = lib.optional (cfg.antialiasing != null) (set "antialias" cfg.antialiasing) ++ lib.optionals (cfg.hinting != null) [ (set "hinting" true) (set "hintstyle" ("hint" + cfg.hinting)) ] ++ lib.optional (cfg.subpixelRendering != null) ( set "rgba" (builtins.replaceStrings [ "ertical-" ] [ "" ] cfg.subpixelRendering) ); in { enable = builtins.length content > 0; priority = 10; source = null; text = mkFontconfigConf ( lib.concatStrings ([ "Set the rendering mode\n" ] ++ content) ); }; default-fonts = let genDefault = fonts: name: lib.optionalString (fonts != [ ]) '' ${name} ${lib.concatStringsSep "" ( map (font: '' ${font} '') fonts )} ''; in { enable = true; priority = 52; source = null; text = mkFontconfigConf '' ${genDefault cfg.defaultFonts.sansSerif "sans-serif"} ${genDefault cfg.defaultFonts.serif "serif"} ${genDefault cfg.defaultFonts.monospace "monospace"} ${genDefault cfg.defaultFonts.emoji "emoji"} ''; }; }; xdg.configFile = lib.mapAttrs' ( name: config: lib.nameValuePair "fontconfig/conf.d/${builtins.toString config.priority}-hm-${config.label}.conf" { inherit (config) enable text; source = lib.mkIf (config.source != null) config.source; } ) cfg.configFile; }; }