1/*
2
3NixOS support 2 fontconfig versions, "support" and "latest".
4
5- "latest" refers to default fontconfig package (pkgs.fontconfig).
6 configuration files are linked to /etc/fonts/VERSION/conf.d/
7- "support" refers to supportPkg (pkgs."fontconfig_${supportVersion}").
8 configuration files are linked to /etc/fonts/conf.d/
9
10This module generates a package containing configuration files and link it in /etc/fonts.
11
12Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing.
13Low number means high priority.
14
15*/
16
17{ config, lib, pkgs, ... }:
18
19with lib;
20
21let cfg = config.fonts.fontconfig;
22
23 fcBool = x: "<bool>" + (boolToString x) + "</bool>";
24
25 # back-supported fontconfig version and package
26 # version is used for font cache generation
27 supportVersion = "210";
28 supportPkg = pkgs."fontconfig_${supportVersion}";
29
30 # latest fontconfig version and package
31 # version is used for configuration folder name, /etc/fonts/VERSION/
32 # note: format differs from supportVersion and can not be used with makeCacheConf
33 latestVersion = pkgs.fontconfig.configVersion;
34 latestPkg = pkgs.fontconfig;
35
36 # supported version fonts.conf
37 supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
38
39 # configuration file to read fontconfig cache
40 # version dependent
41 # priority 0
42 cacheConfSupport = makeCacheConf { version = supportVersion; };
43 cacheConfLatest = makeCacheConf {};
44
45 # generate the font cache setting file for a fontconfig version
46 # use latest when no version is passed
47 makeCacheConf = { version ? null }:
48 let
49 fcPackage = if builtins.isNull version
50 then "fontconfig"
51 else "fontconfig_${version}";
52 makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
53 cache = makeCache pkgs."${fcPackage}";
54 cache32 = makeCache pkgs.pkgsi686Linux."${fcPackage}";
55 in
56 pkgs.writeText "fc-00-nixos-cache.conf" ''
57 <?xml version='1.0'?>
58 <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
59 <fontconfig>
60 <!-- Font directories -->
61 ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
62 <!-- Pre-generated font caches -->
63 <cachedir>${cache}</cachedir>
64 ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
65 <cachedir>${cache32}</cachedir>
66 ''}
67 </fontconfig>
68 '';
69
70 # rendering settings configuration file
71 # priority 10
72 renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
73 <?xml version='1.0'?>
74 <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
75 <fontconfig>
76
77 <!-- Default rendering settings -->
78 <match target="pattern">
79 <edit mode="append" name="hinting">
80 ${fcBool cfg.hinting.enable}
81 </edit>
82 <edit mode="append" name="autohint">
83 ${fcBool cfg.hinting.autohint}
84 </edit>
85 <edit mode="append" name="hintstyle">
86 <const>hintslight</const>
87 </edit>
88 <edit mode="append" name="antialias">
89 ${fcBool cfg.antialias}
90 </edit>
91 <edit mode="append" name="rgba">
92 <const>${cfg.subpixel.rgba}</const>
93 </edit>
94 <edit mode="append" name="lcdfilter">
95 <const>lcd${cfg.subpixel.lcdfilter}</const>
96 </edit>
97 </match>
98
99 ${optionalString (cfg.dpi != 0) ''
100 <match target="pattern">
101 <edit name="dpi" mode="assign">
102 <double>${toString cfg.dpi}</double>
103 </edit>
104 </match>
105 ''}
106
107 </fontconfig>
108 '';
109
110 # local configuration file
111 # priority 51
112 localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
113
114 # default fonts configuration file
115 # priority 52
116 defaultFontsConf =
117 let genDefault = fonts: name:
118 optionalString (fonts != []) ''
119 <alias>
120 <family>${name}</family>
121 <prefer>
122 ${concatStringsSep ""
123 (map (font: ''
124 <family>${font}</family>
125 '') fonts)}
126 </prefer>
127 </alias>
128 '';
129 in
130 pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
131 <?xml version='1.0'?>
132 <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
133 <fontconfig>
134
135 <!-- Default fonts -->
136 ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
137
138 ${genDefault cfg.defaultFonts.serif "serif"}
139
140 ${genDefault cfg.defaultFonts.monospace "monospace"}
141
142 </fontconfig>
143 '';
144
145 # bitmap font options
146 # priority 53
147 rejectBitmaps = pkgs.writeText "fc-53-nixos-bitmaps.conf" ''
148 <?xml version="1.0"?>
149 <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
150 <fontconfig>
151
152 ${optionalString (!cfg.allowBitmaps) ''
153 <!-- Reject bitmap fonts -->
154 <selectfont>
155 <rejectfont>
156 <pattern>
157 <patelt name="scalable"><bool>false</bool></patelt>
158 </pattern>
159 </rejectfont>
160 </selectfont>
161 ''}
162
163 <!-- Use embedded bitmaps in fonts like Calibri? -->
164 <match target="font">
165 <edit name="embeddedbitmap" mode="assign">
166 ${fcBool cfg.useEmbeddedBitmaps}
167 </edit>
168 </match>
169
170 </fontconfig>
171 '';
172
173 # reject Type 1 fonts
174 # priority 53
175 rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
176 <?xml version="1.0"?>
177 <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
178 <fontconfig>
179
180 <!-- Reject Type 1 fonts -->
181 <selectfont>
182 <rejectfont>
183 <pattern>
184 <patelt name="fontformat"><string>Type 1</string></patelt>
185 </pattern>
186 </rejectfont>
187 </selectfont>
188
189 </fontconfig>
190 '';
191
192 # fontconfig configuration package
193 confPkg = pkgs.runCommand "fontconfig-conf" {} ''
194 support_folder=$out/etc/fonts
195 latest_folder=$out/etc/fonts/${latestVersion}
196
197 mkdir -p $support_folder/conf.d
198 mkdir -p $latest_folder/conf.d
199
200 # fonts.conf
201 ln -s ${supportFontsConf} $support_folder/fonts.conf
202 ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
203 $latest_folder/fonts.conf
204
205 # fontconfig default config files
206 ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
207 $support_folder/conf.d/
208 ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
209 $latest_folder/conf.d/
210
211 # update latest 51-local.conf path to look at the latest local.conf
212 rm $latest_folder/conf.d/51-local.conf
213
214 substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
215 $latest_folder/conf.d/51-local.conf \
216 --replace local.conf /etc/fonts/${latestVersion}/local.conf
217
218 # 00-nixos-cache.conf
219 ln -s ${cacheConfSupport} \
220 $support_folder/conf.d/00-nixos-cache.conf
221 ln -s ${cacheConfLatest} $latest_folder/conf.d/00-nixos-cache.conf
222
223 # 10-nixos-rendering.conf
224 ln -s ${renderConf} $support_folder/conf.d/10-nixos-rendering.conf
225 ln -s ${renderConf} $latest_folder/conf.d/10-nixos-rendering.conf
226
227 # 50-user.conf
228 ${optionalString (! cfg.includeUserConf) ''
229 rm $support_folder/conf.d/50-user.conf
230 rm $latest_folder/conf.d/50-user.conf
231 ''}
232
233 # local.conf (indirect priority 51)
234 ${optionalString (cfg.localConf != "") ''
235 ln -s ${localConf} $support_folder/local.conf
236 ln -s ${localConf} $latest_folder/local.conf
237 ''}
238
239 # 52-nixos-default-fonts.conf
240 ln -s ${defaultFontsConf} $support_folder/conf.d/52-nixos-default-fonts.conf
241 ln -s ${defaultFontsConf} $latest_folder/conf.d/52-nixos-default-fonts.conf
242
243 # 53-nixos-bitmaps.conf
244 ln -s ${rejectBitmaps} $support_folder/conf.d/53-nixos-bitmaps.conf
245 ln -s ${rejectBitmaps} $latest_folder/conf.d/53-nixos-bitmaps.conf
246
247 ${optionalString (! cfg.allowType1) ''
248 # 53-nixos-reject-type1.conf
249 ln -s ${rejectType1} $support_folder/conf.d/53-nixos-reject-type1.conf
250 ln -s ${rejectType1} $latest_folder/conf.d/53-nixos-reject-type1.conf
251 ''}
252 '';
253
254 # Package with configuration files
255 # this merge all the packages in the fonts.fontconfig.confPackages list
256 fontconfigEtc = pkgs.buildEnv {
257 name = "fontconfig-etc";
258 paths = cfg.confPackages;
259 ignoreCollisions = true;
260 };
261in
262{
263
264 options = {
265
266 fonts = {
267
268 fontconfig = {
269 enable = mkOption {
270 type = types.bool;
271 default = true;
272 description = ''
273 If enabled, a Fontconfig configuration file will be built
274 pointing to a set of default fonts. If you don't care about
275 running X11 applications or any other program that uses
276 Fontconfig, you can turn this option off and prevent a
277 dependency on all those fonts.
278 '';
279 };
280
281 confPackages = mkOption {
282 internal = true;
283 type = with types; listOf path;
284 default = [ ];
285 description = ''
286 Fontconfig configuration packages.
287 '';
288 };
289
290 antialias = mkOption {
291 type = types.bool;
292 default = true;
293 description = ''
294 Enable font antialiasing. At high resolution (> 200 DPI),
295 antialiasing has no visible effect; users of such displays may want
296 to disable this option.
297 '';
298 };
299
300 dpi = mkOption {
301 type = types.int;
302 default = 0;
303 description = ''
304 Force DPI setting. Setting to <literal>0</literal> disables DPI
305 forcing; the DPI detected for the display will be used.
306 '';
307 };
308
309 localConf = mkOption {
310 type = types.lines;
311 default = "";
312 description = ''
313 System-wide customization file contents, has higher priority than
314 <literal>defaultFonts</literal> settings.
315 '';
316 };
317
318 defaultFonts = {
319 monospace = mkOption {
320 type = types.listOf types.str;
321 default = ["DejaVu Sans Mono"];
322 description = ''
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 = ''
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 = ''
341 System-wide default serif font(s). Multiple fonts may be listed
342 in case multiple languages must be supported.
343 '';
344 };
345 };
346
347 hinting = {
348 enable = mkOption {
349 type = types.bool;
350 default = true;
351 description = ''
352 Enable font hinting. Hinting aligns glyphs to pixel boundaries to
353 improve rendering sharpness at low resolution. At high resolution
354 (> 200 dpi) hinting will do nothing (at best); users of such
355 displays may want to disable this option.
356 '';
357 };
358
359 autohint = mkOption {
360 type = types.bool;
361 default = false;
362 description = ''
363 Enable the autohinter in place of the default interpreter.
364 The results are usually lower quality than correctly-hinted
365 fonts, but better than unhinted fonts.
366 '';
367 };
368 };
369
370 includeUserConf = mkOption {
371 type = types.bool;
372 default = true;
373 description = ''
374 Include the user configuration from
375 <filename>~/.config/fontconfig/fonts.conf</filename> or
376 <filename>~/.config/fontconfig/conf.d</filename>.
377 '';
378 };
379
380 subpixel = {
381
382 rgba = mkOption {
383 default = "rgb";
384 type = types.enum ["rgb" "bgr" "vrgb" "vbgr" "none"];
385 description = ''
386 Subpixel order. The overwhelming majority of displays are
387 <literal>rgb</literal> in their normal orientation. Select
388 <literal>vrgb</literal> for mounting such a display 90 degrees
389 clockwise from its normal orientation or <literal>vbgr</literal>
390 for mounting 90 degrees counter-clockwise. Select
391 <literal>bgr</literal> in the unlikely event of mounting 180
392 degrees from the normal orientation. Reverse these directions in
393 the improbable event that the display's native subpixel order is
394 <literal>bgr</literal>.
395 '';
396 };
397
398 lcdfilter = mkOption {
399 default = "default";
400 type = types.enum ["none" "default" "light" "legacy"];
401 description = ''
402 FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering
403 has no visible effect; users of such displays may want to select
404 <literal>none</literal>.
405 '';
406 };
407
408 };
409
410 cache32Bit = mkOption {
411 default = false;
412 type = types.bool;
413 description = ''
414 Generate system fonts cache for 32-bit applications.
415 '';
416 };
417
418 allowBitmaps = mkOption {
419 type = types.bool;
420 default = true;
421 description = ''
422 Allow bitmap fonts. Set to <literal>false</literal> to ban all
423 bitmap fonts.
424 '';
425 };
426
427 allowType1 = mkOption {
428 type = types.bool;
429 default = false;
430 description = ''
431 Allow Type-1 fonts. Default is <literal>false</literal> because of
432 poor rendering.
433 '';
434 };
435
436 useEmbeddedBitmaps = mkOption {
437 type = types.bool;
438 default = false;
439 description = ''Use embedded bitmaps in fonts like Calibri.'';
440 };
441
442 };
443
444 };
445
446 };
447 config = mkMerge [
448 (mkIf cfg.enable {
449 environment.systemPackages = [ pkgs.fontconfig ];
450 environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
451 })
452 (mkIf (cfg.enable && !cfg.penultimate.enable) {
453 fonts.fontconfig.confPackages = [ confPkg ];
454 })
455 ];
456
457}