1/*
2
3Configuration files are linked to /etc/fonts/conf.d/
4
5This module generates a package containing configuration files and link it in /etc/fonts.
6
7Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing.
8Low number means high priority.
9
10NOTE: Please take extreme care when adjusting the default settings of this module.
11People care a lot, and I mean A LOT, about their font rendering, and you will be
12The Person That Broke It if it changes in a way people don't like.
13
14See prior art:
15- https://github.com/NixOS/nixpkgs/pull/194594
16- https://github.com/NixOS/nixpkgs/pull/222236
17- https://github.com/NixOS/nixpkgs/pull/222689
18
19And do not repeat our mistakes.
20
21- @K900, March 2023
22
23*/
24
25{ config, pkgs, lib, ... }:
26
27with lib;
28
29let
30 cfg = config.fonts.fontconfig;
31
32 fcBool = x: "<bool>" + (boolToString x) + "</bool>";
33 pkg = pkgs.fontconfig;
34
35 # configuration file to read fontconfig cache
36 # priority 0
37 cacheConf = makeCacheConf {};
38
39 # generate the font cache setting file
40 # When cross-compiling, we can’t generate the cache, so we skip the
41 # <cachedir> part. fontconfig still works but is a little slower in
42 # looking things up.
43 makeCacheConf = { }:
44 let
45 makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.packages; };
46 cache = makeCache pkgs.fontconfig;
47 cache32 = makeCache pkgs.pkgsi686Linux.fontconfig;
48 in
49 pkgs.writeText "fc-00-nixos-cache.conf" ''
50 <?xml version='1.0'?>
51 <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
52 <fontconfig>
53 <!-- Font directories -->
54 ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.packages)}
55 ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
56 <!-- Pre-generated font caches -->
57 <cachedir>${cache}</cachedir>
58 ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
59 <cachedir>${cache32}</cachedir>
60 ''}
61 ''}
62 </fontconfig>
63 '';
64
65 # rendering settings configuration file
66 # priority 10
67 renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
68 <?xml version='1.0'?>
69 <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
70 <fontconfig>
71
72 <!-- Default rendering settings -->
73 <match target="pattern">
74 <edit mode="append" name="hinting">
75 ${fcBool cfg.hinting.enable}
76 </edit>
77 <edit mode="append" name="autohint">
78 ${fcBool cfg.hinting.autohint}
79 </edit>
80 </match>
81
82 </fontconfig>
83 '';
84
85 # local configuration file
86 localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
87
88 # default fonts configuration file
89 # priority 52
90 defaultFontsConf =
91 let genDefault = fonts: name:
92 optionalString (fonts != []) ''
93 <alias binding="same">
94 <family>${name}</family>
95 <prefer>
96 ${concatStringsSep ""
97 (map (font: ''
98 <family>${font}</family>
99 '') fonts)}
100 </prefer>
101 </alias>
102 '';
103 in
104 pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
105 <?xml version='1.0'?>
106 <!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
107 <fontconfig>
108
109 <!-- Default fonts -->
110 ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
111
112 ${genDefault cfg.defaultFonts.serif "serif"}
113
114 ${genDefault cfg.defaultFonts.monospace "monospace"}
115
116 ${genDefault cfg.defaultFonts.emoji "emoji"}
117
118 </fontconfig>
119 '';
120
121 # bitmap font options
122 # priority 53
123 rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" ''
124 <?xml version="1.0"?>
125 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
126 <fontconfig>
127
128 ${optionalString (!cfg.allowBitmaps) ''
129 <!-- Reject bitmap fonts -->
130 <selectfont>
131 <rejectfont>
132 <pattern>
133 <patelt name="scalable"><bool>false</bool></patelt>
134 </pattern>
135 </rejectfont>
136 </selectfont>
137 ''}
138
139 <!-- Use embedded bitmaps in fonts like Calibri? -->
140 <match target="font">
141 <edit name="embeddedbitmap" mode="assign">
142 ${fcBool cfg.useEmbeddedBitmaps}
143 </edit>
144 </match>
145
146 </fontconfig>
147 '';
148
149 # reject Type 1 fonts
150 # priority 53
151 rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
152 <?xml version="1.0"?>
153 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
154 <fontconfig>
155
156 <!-- Reject Type 1 fonts -->
157 <selectfont>
158 <rejectfont>
159 <pattern>
160 <patelt name="fontformat"><string>Type 1</string></patelt>
161 </pattern>
162 </rejectfont>
163 </selectfont>
164
165 </fontconfig>
166 '';
167
168 # Replace default linked config with a different variant
169 replaceDefaultConfig = defaultConfig: newConfig: ''
170 rm $dst/${defaultConfig}
171 ln -s ${pkg.out}/share/fontconfig/conf.avail/${newConfig} \
172 $dst/
173 '';
174
175 # fontconfig configuration package
176 confPkg = pkgs.runCommand "fontconfig-conf" {
177 preferLocalBuild = true;
178 } ''
179 dst=$out/etc/fonts/conf.d
180 mkdir -p $dst
181
182 # fonts.conf
183 ln -s ${pkg.out}/etc/fonts/fonts.conf \
184 $dst/../fonts.conf
185 # TODO: remove this legacy symlink once people stop using packages built before #95358 was merged
186 mkdir -p $out/etc/fonts/2.11
187 ln -s /etc/fonts/fonts.conf \
188 $out/etc/fonts/2.11/fonts.conf
189
190 # fontconfig default config files
191 ln -s ${pkg.out}/etc/fonts/conf.d/*.conf \
192 $dst/
193
194 ${optionalString (!cfg.antialias)
195 (replaceDefaultConfig "10-yes-antialias.conf"
196 "10-no-antialias.conf")
197 }
198
199 ${optionalString (cfg.hinting.style != "slight")
200 (replaceDefaultConfig "10-hinting-slight.conf"
201 "10-hinting-${cfg.hinting.style}.conf")
202 }
203
204 ${optionalString (cfg.subpixel.rgba != "none")
205 (replaceDefaultConfig "10-sub-pixel-none.conf"
206 "10-sub-pixel-${cfg.subpixel.rgba}.conf")
207 }
208
209 ${optionalString (cfg.subpixel.lcdfilter != "default")
210 (replaceDefaultConfig "11-lcdfilter-default.conf"
211 "11-lcdfilter-${cfg.subpixel.lcdfilter}.conf")
212 }
213
214 # 00-nixos-cache.conf
215 ln -s ${cacheConf} $dst/00-nixos-cache.conf
216
217 # 10-nixos-rendering.conf
218 ln -s ${renderConf} $dst/10-nixos-rendering.conf
219
220 # 50-user.conf
221 ${optionalString (!cfg.includeUserConf) ''
222 rm $dst/50-user.conf
223 ''}
224
225 # local.conf (indirect priority 51)
226 ${optionalString (cfg.localConf != "") ''
227 ln -s ${localConf} $dst/../local.conf
228 ''}
229
230 # 52-nixos-default-fonts.conf
231 ln -s ${defaultFontsConf} $dst/52-nixos-default-fonts.conf
232
233 # 53-no-bitmaps.conf
234 ln -s ${rejectBitmaps} $dst/53-no-bitmaps.conf
235
236 ${optionalString (!cfg.allowType1) ''
237 # 53-nixos-reject-type1.conf
238 ln -s ${rejectType1} $dst/53-nixos-reject-type1.conf
239 ''}
240 '';
241
242 # Package with configuration files
243 # this merge all the packages in the fonts.fontconfig.confPackages list
244 fontconfigEtc = pkgs.buildEnv {
245 name = "fontconfig-etc";
246 paths = cfg.confPackages;
247 ignoreCollisions = true;
248 };
249
250 fontconfigNote = "Consider manually configuring fonts.fontconfig according to personal preference.";
251in
252{
253 imports = [
254 (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ])
255 (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ])
256 (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
257 (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
258 (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
259 (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
260 (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
261 (mkRemovedOptionModule [ "fonts" "fontconfig" "dpi" ] "Use display server-specific options")
262 (mkRemovedOptionModule [ "hardware" "video" "hidpi" "enable" ] fontconfigNote)
263 (mkRemovedOptionModule [ "fonts" "optimizeForVeryHighDPI" ] fontconfigNote)
264 ] ++ lib.forEach [ "enable" "substitutions" "preset" ]
265 (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
266 The fonts.fontconfig.ultimate module and configuration is obsolete.
267 The repository has since been archived and activity has ceased.
268 https://github.com/bohoomil/fontconfig-ultimate/issues/171.
269 No action should be needed for font configuration, as the fonts.fontconfig
270 module is already used by default.
271 '');
272
273 options = {
274
275 fonts = {
276
277 fontconfig = {
278 enable = mkOption {
279 type = types.bool;
280 default = true;
281 description = lib.mdDoc ''
282 If enabled, a Fontconfig configuration file will be built
283 pointing to a set of default fonts. If you don't care about
284 running X11 applications or any other program that uses
285 Fontconfig, you can turn this option off and prevent a
286 dependency on all those fonts.
287 '';
288 };
289
290 confPackages = mkOption {
291 internal = true;
292 type = with types; listOf path;
293 default = [ ];
294 description = lib.mdDoc ''
295 Fontconfig configuration packages.
296 '';
297 };
298
299 antialias = mkOption {
300 type = types.bool;
301 default = true;
302 description = lib.mdDoc ''
303 Enable font antialiasing. At high resolution (> 200 DPI),
304 antialiasing has no visible effect; users of such displays may want
305 to disable this option.
306 '';
307 };
308
309 localConf = mkOption {
310 type = types.lines;
311 default = "";
312 description = lib.mdDoc ''
313 System-wide customization file contents, has higher priority than
314 `defaultFonts` settings.
315 '';
316 };
317
318 defaultFonts = {
319 monospace = mkOption {
320 type = types.listOf types.str;
321 default = ["DejaVu Sans Mono"];
322 description = lib.mdDoc ''
323 System-wide default monospace font(s). Multiple fonts may be
324 listed in case multiple languages must be supported.
325 '';
326 };
327
328 sansSerif = mkOption {
329 type = types.listOf types.str;
330 default = ["DejaVu Sans"];
331 description = lib.mdDoc ''
332 System-wide default sans serif font(s). Multiple fonts may be
333 listed in case multiple languages must be supported.
334 '';
335 };
336
337 serif = mkOption {
338 type = types.listOf types.str;
339 default = ["DejaVu Serif"];
340 description = lib.mdDoc ''
341 System-wide default serif font(s). Multiple fonts may be listed
342 in case multiple languages must be supported.
343 '';
344 };
345
346 emoji = mkOption {
347 type = types.listOf types.str;
348 default = ["Noto Color Emoji"];
349 description = lib.mdDoc ''
350 System-wide default emoji font(s). Multiple fonts may be listed
351 in case a font does not support all emoji.
352
353 Note that fontconfig matches color emoji fonts preferentially,
354 so if you want to use a black and white font while having
355 a color font installed (eg. Noto Color Emoji installed alongside
356 Noto Emoji), fontconfig will still choose the color font even
357 when it is later in the list.
358 '';
359 };
360 };
361
362 hinting = {
363 enable = mkOption {
364 type = types.bool;
365 default = true;
366 description = lib.mdDoc ''
367 Enable font hinting. Hinting aligns glyphs to pixel boundaries to
368 improve rendering sharpness at low resolution. At high resolution
369 (> 200 dpi) hinting will do nothing (at best); users of such
370 displays may want to disable this option.
371 '';
372 };
373
374 autohint = mkOption {
375 type = types.bool;
376 default = false;
377 description = lib.mdDoc ''
378 Enable the autohinter in place of the default interpreter.
379 The results are usually lower quality than correctly-hinted
380 fonts, but better than unhinted fonts.
381 '';
382 };
383
384 style = mkOption {
385 type = types.enum ["none" "slight" "medium" "full"];
386 default = "slight";
387 description = lib.mdDoc ''
388 Hintstyle is the amount of font reshaping done to line up
389 to the grid.
390
391 slight will make the font more fuzzy to line up to the grid but
392 will be better in retaining font shape, while full will be a
393 crisp font that aligns well to the pixel grid but will lose a
394 greater amount of font shape.
395 '';
396 apply =
397 val:
398 let
399 from = "fonts.fontconfig.hinting.style";
400 val' = lib.removePrefix "hint" val;
401 warning = "The option `${from}` contains a deprecated value `${val}`. Use `${val'}` instead.";
402 in
403 lib.warnIf (lib.hasPrefix "hint" val) warning val';
404 };
405 };
406
407 includeUserConf = mkOption {
408 type = types.bool;
409 default = true;
410 description = lib.mdDoc ''
411 Include the user configuration from
412 {file}`~/.config/fontconfig/fonts.conf` or
413 {file}`~/.config/fontconfig/conf.d`.
414 '';
415 };
416
417 subpixel = {
418
419 rgba = mkOption {
420 default = "none";
421 type = types.enum ["rgb" "bgr" "vrgb" "vbgr" "none"];
422 description = lib.mdDoc ''
423 Subpixel order. The overwhelming majority of displays are
424 `rgb` in their normal orientation. Select
425 `vrgb` for mounting such a display 90 degrees
426 clockwise from its normal orientation or `vbgr`
427 for mounting 90 degrees counter-clockwise. Select
428 `bgr` in the unlikely event of mounting 180
429 degrees from the normal orientation. Reverse these directions in
430 the improbable event that the display's native subpixel order is
431 `bgr`.
432 '';
433 };
434
435 lcdfilter = mkOption {
436 default = "default";
437 type = types.enum ["none" "default" "light" "legacy"];
438 description = lib.mdDoc ''
439 FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering
440 has no visible effect; users of such displays may want to select
441 `none`.
442 '';
443 };
444
445 };
446
447 cache32Bit = mkOption {
448 default = false;
449 type = types.bool;
450 description = lib.mdDoc ''
451 Generate system fonts cache for 32-bit applications.
452 '';
453 };
454
455 allowBitmaps = mkOption {
456 type = types.bool;
457 default = true;
458 description = lib.mdDoc ''
459 Allow bitmap fonts. Set to `false` to ban all
460 bitmap fonts.
461 '';
462 };
463
464 allowType1 = mkOption {
465 type = types.bool;
466 default = false;
467 description = lib.mdDoc ''
468 Allow Type-1 fonts. Default is `false` because of
469 poor rendering.
470 '';
471 };
472
473 useEmbeddedBitmaps = mkOption {
474 type = types.bool;
475 default = false;
476 description = lib.mdDoc "Use embedded bitmaps in fonts like Calibri.";
477 };
478
479 };
480
481 };
482
483 };
484 config = mkMerge [
485 (mkIf cfg.enable {
486 environment.systemPackages = [ pkgs.fontconfig ];
487 environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
488 security.apparmor.includes."abstractions/fonts" = ''
489 # fonts.conf
490 r ${pkg.out}/etc/fonts/fonts.conf,
491
492 # fontconfig default config files
493 r ${pkg.out}/etc/fonts/conf.d/*.conf,
494
495 # 00-nixos-cache.conf
496 r ${cacheConf},
497
498 # 10-nixos-rendering.conf
499 r ${renderConf},
500
501 # 50-user.conf
502 ${optionalString cfg.includeUserConf ''
503 r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
504 ''}
505
506 # local.conf (indirect priority 51)
507 ${optionalString (cfg.localConf != "") ''
508 r ${localConf},
509 ''}
510
511 # 52-nixos-default-fonts.conf
512 r ${defaultFontsConf},
513
514 # 53-no-bitmaps.conf
515 r ${rejectBitmaps},
516
517 ${optionalString (!cfg.allowType1) ''
518 # 53-nixos-reject-type1.conf
519 r ${rejectType1},
520 ''}
521 '';
522 })
523 (mkIf cfg.enable {
524 fonts.fontconfig.confPackages = [ confPkg ];
525 })
526 ];
527
528}