AtlatestRepositorycore-channel

core-channel / tree / src / core / packagesfonts.sgl

1;;; Font rasterisation and font configuration.
2;;;
3;;; This is the half of foot's closure that has to reach OUTSIDE the store to
4;;; be useful, and it is the one place in this chain where "hermetic" and
5;;; "works on the user's desktop" pull against each other. Wayland does not:
6;;; a client and a compositor exchange a wire protocol over a socket and never
7;;; share a library, so version skew there is a non-issue. Fonts are
8;;; different -- a fontconfig built into the store looks for font FILES, and
9;;; the font files a user has are wherever their system put them.
10;;;
11;;; Nothing here solves that; it is recorded so the next reader does not
12;;; mistake a font-path gap for a foot defect. See the task note for the
13;;; measured runtime behaviour.
14(define-library (core packages fonts)
15 (import (sigil core)
16 (core build-environments)
17 (core packages compression)
18 (core packages xml)
19 (core packages graphics)
20 (core packages datastructures)
21 (core packages build-tools)
22 (core packages python)
23 (sigil env package)
24 (sigil build external))
25 (export freetype fontconfig fcft)
26 (begin
27 ;; freetype, with every optional backend OFF.
28 ;;
29 ;; The harfbuzz one is not a size decision, it is a CYCLE: harfbuzz builds
30 ;; against freetype and freetype can build against harfbuzz for
31 ;; auto-hinting. Distributions resolve it by building freetype twice.
32 ;; This channel does not need to, because nothing here does complex text
33 ;; shaping -- foot is configured without grapheme clustering, which is what
34 ;; would have pulled harfbuzz in through fcft.
35 (define freetype
36 (package
37 name: "freetype"
38 version: "2.13.3"
39 source: (external-source
40 location: "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.3.tar.xz"
41 sha256: "0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289")
42 buildenv: core-buildenv
43 dependencies: (list zlib)
44 phases: (list
45 (external-phase
46 kind: 'configure
47 args: (list "--prefix=${out}"
48 "--disable-shared"
49 "--enable-static"
50 "--with-zlib=yes"
51 "--with-bzip2=no"
52 "--with-png=no"
53 "--with-brotli=no"
54 "--with-harfbuzz=no"))
55 (external-phase kind: 'make)
56 (external-phase kind: 'install)
57 ;; fontconfig asks for `freetype2 >= 21.0.15` through
58 ;; PKG_CHECK_MODULES and fcft asks for it too, so the `.pc`
59 ;; is load-bearing rather than decorative. Note the version
60 ;; in that constraint is freetype's LIBTOOL version, not its
61 ;; release version -- 2.13.3 publishes 26.x -- which is why
62 ;; the check below asserts the file exists and does not try
63 ;; to be clever about its contents.
64 (external-phase
65 kind: 'run
66 args: (list "sh" "-c"
67 (string-append
68 "set -eu\n"
69 "d=\"$(dirname \"$PWD\")/install-root\"\n"
70 "test -f \"$d${out}/lib/pkgconfig/freetype2.pc\"\n"
71 "echo FREETYPE2-PC-PRESENT\n"))))))
73 ;; VERSION AND SOURCE, because both were forced and the reasoning matters.
74 ;;
75 ;; `www.freedesktop.org/software/fontconfig/release/` -- the URL every
76 ;; older recipe uses -- was unreachable from this host across three
77 ;; attempts spanning ten minutes. Upstream has moved distribution to
78 ;; the freedesktop GitLab generic package registry, which is where
79 ;; Alpine now points too, and that registry does NOT carry 2.15.0.
80 ;;
81 ;; So the choice was between 2.15.0 from a DISTRIBUTION MIRROR and
82 ;; 2.18.1 from the CANONICAL upstream host. Taking the canonical host
83 ;; keeps a real upstream release artifact on the critical path, which
84 ;; is the rule this channel is built on, and it is a current release
85 ;; rather than a two-year-old one.
86 ;;
87 ;; 2.18.1 SHIPS BOTH BUILD SYSTEMS, and this build deliberately uses
88 ;; the autotools one. Its meson.build pulls in Rust for the optional
89 ;; `fc-fontations` backend (meson.build:92, add_languages([rust]));
90 ;; configure.ac mentions Rust nowhere. Autotools therefore avoids
91 ;; introducing a whole second language toolchain to this channel for a
92 ;; feature foot does not use.
93 ;;
94 ;; fontconfig. Three build-time tools and two libraries, and the tool list
95 ;; is the interesting part:
96 ;;
97 ;; gperf -- configure.ac:216 RUNS it: `echo 'foo' | gperf -L ANSI-C`.
98 ;; Absent, the substitution is empty, the two compile probes
99 ;; that follow test an empty snippet, the first succeeds, and
100 ;; configure concludes FC_GPERF_SIZE_T=size_t from a probe
101 ;; that measured nothing.
102 ;; python -- configure.ac:56 is AM_PATH_PYTHON([3]).
103 ;;
104 ;; Both are `build-inputs:` and not `dependencies:`, because neither is
105 ;; linked into the result and neither may contribute a store path to it.
106 (define fontconfig
107 (package
108 name: "fontconfig"
109 version: "2.18.1"
110 source: (external-source
111 location: "https://gitlab.freedesktop.org/api/v4/projects/890/packages/generic/fontconfig/2.18.1/fontconfig-2.18.1.tar.xz"
112 sha256: "2300f3dbfa7253b3a44f4feecdbc8dfa45dde5dc2cfb71fceaf31f394cb41031")
113 buildenv: core-buildenv
114 ;; ZLIB IS DECLARED HERE ALTHOUGH FONTCONFIG NEVER MENTIONS IT, AND THE
115 ;; REASON IS A PROPERTY OF THIS CHANNEL RATHER THAN OF FONTCONFIG.
116 ;;
117 ;; `dependency-pkgconfig` in (sigil build external) composes
118 ;; PKG_CONFIG_PATH from DIRECT dependencies only -- one
119 ;; `<dep>/lib/pkgconfig` entry each, with no transitive walk. freetype's
120 ;; generated `freetype2.pc` carries `Requires: zlib`, so pkg-config
121 ;; resolving `freetype2` must also resolve `zlib`, and without this line
122 ;; it cannot:
123 ;;
124 ;; checking for FREETYPE... no
125 ;; configure: error: Package requirements (freetype2 >= 21.0.15) were
126 ;; not met: Package 'zlib', required by 'freetype2', not found
127 ;;
128 ;; THE DIAGNOSTIC NAMES FREETYPE AND THE MISSING PACKAGE IS ZLIB, which
129 ;; is the part that costs the time.
130 ;;
131 ;; This is not a workaround. It is the same rule the builder applies to
132 ;; `${name}` interpolation -- a build that wants to reach a
133 ;; grand-dependency has to declare it -- and it is TRUE here: everything
134 ;; in this channel links statically, so zlib really is on fontconfig's
135 ;; link line and really is in its closure. Every consumer of a `.pc`
136 ;; with a `Requires:` chain inherits this.
137 dependencies: (list freetype expat zlib)
138 build-inputs: (list gperf python)
139 phases: (list
140 (external-phase
141 kind: 'configure
142 args: (list "--prefix=${out}"
143 "--disable-shared"
144 "--enable-static"
145 ;; The docs want docbook and would otherwise be
146 ;; a probe on what is installed.
147 "--disable-docs"
148 ;; Nothing in this channel reads a cache
149 ;; fontconfig would have to build at install
150 ;; time, and running fc-cache during a build
151 ;; would write outside the staged prefix.
152 "--disable-cache-build"
153 ;; NLS wants gettext, which is not in the seed
154 ;; and which nothing here reads.
155 "--disable-nls"))
156 (external-phase kind: 'make)
157 (external-phase kind: 'install)
158 (external-phase
159 kind: 'run
160 args: (list "sh" "-c"
161 (string-append
162 "set -eu\n"
163 "d=\"$(dirname \"$PWD\")/install-root\"\n"
164 "test -f \"$d${out}/lib/pkgconfig/fontconfig.pc\"\n"
165 ;; The config file is what a runtime consumer
166 ;; needs and what the font-path problem above is
167 ;; about; assert it landed so a missing one is
168 ;; found here rather than at first launch.
169 "test -f \"$d${out}/etc/fonts/fonts.conf\"\n"
170 "echo FONTCONFIG-PC-AND-CONF-PRESENT\n"))))))
172 ;; fcft: the glyph rasterisation and caching layer between foot and
173 ;; freetype/fontconfig. By the same author as foot, and foot's meson.build
174 ;; carries a `[wrap-git]` fallback for it -- which cannot resolve inside a
175 ;; sandbox, so a real package is required rather than optional.
176 ;;
177 ;; TEXT SHAPING IS OFF, AND THAT IS THE LARGEST SCOPE DECISION IN THIS
178 ;; CHAIN. `grapheme-shaping` and `run-shaping` each pull in harfbuzz, and
179 ;; run-shaping additionally pulls utf8proc. Turning both off removes two
180 ;; packages, and harfbuzz would additionally have re-opened the
181 ;; freetype/harfbuzz build cycle that `freetype` above is configured to
182 ;; avoid.
183 ;;
184 ;; WHAT IS LOST, said plainly rather than buried: without shaping, foot
185 ;; renders each grapheme cluster from a single font glyph. Combining
186 ;; sequences -- emoji built from ZWJ sequences, some scripts with complex
187 ;; joining behaviour -- will not compose. Latin, Cyrillic, Greek, CJK and
188 ;; box drawing are unaffected. Turn it back on by packaging harfbuzz and
189 ;; utf8proc and flipping these two options here and in `foot`.
190 ;;
191 ;; The SVG backend is left at its default, which uses the bundled nanosvg
192 ;; and therefore costs no package.
193 (define fcft
194 (package
195 name: "fcft"
196 version: "3.3.3"
197 source: (external-source
198 location: "https://codeberg.org/dnkl/fcft/releases/download/3.3.3/fcft-3.3.3.tar.gz"
199 sha256: "bb298772d625e7d917373d541456f34f1957a9e22b16f17f64158b2c3816563c")
200 buildenv: core-buildenv
201 ;; zlib is here for the reason spelled out on fontconfig above: it is
202 ;; reached through freetype2.pc's `Requires:` line, and this channel
203 ;; composes PKG_CONFIG_PATH from direct dependencies only.
204 dependencies: (list fontconfig freetype pixman tllist expat zlib)
205 build-inputs: (list meson samurai python)
206 phases: (list
207 (external-phase
208 kind: 'run
209 args: (list "sh" "-c"
210 (string-append
211 "set -eu\n"
212 "meson setup build"
213 " --prefix=${out}"
214 " --buildtype=plain"
215 " --default-library=static"
216 ;; `-Dprefer_static=true` MAKES MESON ASK
217 ;; PKG-CONFIG THE STATIC QUESTION, and without it
218 ;; a fully static link fails at the last step.
219 ;;
220 ;; A `.pc` file splits its link line: `Libs:` is
221 ;; what a SHARED consumer needs, `Libs.private:`
222 ;; and `Requires.private:` are the extra
223 ;; transitive libraries a STATIC consumer needs.
224 ;; fontconfig lists expat under
225 ;; `Requires.private:`, so a default
226 ;; `dependency('fontconfig')` emits no `-lexpat`
227 ;; -- correct for a shared link, fatal here:
228 ;;
229 ;; ld.lld: error: undefined symbol: XML_ErrorString
230 ;; >>> referenced by fcxml.c
231 ;; >>> fcxml.o in archive libfontconfig.a
232 ;;
233 ;; The symbol is missing from the LINK, not from
234 ;; the closure: expat is declared, built, and
235 ;; present. Only the query was the wrong one.
236 ;; This is the same `Requires.private:` line that
237 ;; had to be satisfied at CONFIGURE time; it bites
238 ;; twice, in two different ways, and fixing the
239 ;; first does not fix the second.
240 " -Dprefer_static=true"
241 " -Dgrapheme-shaping=disabled"
242 " -Drun-shaping=disabled"
243 " -Ddocs=disabled"
244 " -Dexamples=false"
245 " -Dtest-text-shaping=false\n"
246 "echo FCFT-CONFIGURED\n")))
247 (external-phase
248 kind: 'run
249 args: (list "sh" "-c"
250 (string-append
251 "set -eu\n"
252 "meson compile -C build\n"
253 "echo FCFT-COMPILED\n")))
254 (external-phase
255 kind: 'run
256 args: (list "sh" "-c"
257 (string-append
258 "set -eu\n"
259 "d=\"$(dirname \"$PWD\")/install-root\"\n"
260 "DESTDIR=\"$d\" meson install -C build --no-rebuild\n"
261 "test -f \"$d${out}/lib/pkgconfig/fcft.pc\"\n"
262 ;; foot pins fcft to >=3.3.1 <4.0.0, so the
263 ;; version pkg-config reports is load-bearing and
264 ;; is asserted rather than assumed.
265 ;; `--modversion` PRINTS a version; it does not
266 ;; CHECK one, and it exits 0 for any value. foot
267 ;; pins fcft to `>=3.3.1 <4.0.0`, so assert that
268 ;; range with the options that actually compare.
269 ;; Printing it as well keeps the value in the log.
270 "v=$(PKG_CONFIG_PATH=\"$d${out}/lib/pkgconfig\" pkg-config --modversion fcft)\n"
271 "echo \"FCFT-VERSION=[$v]\"\n"
272 "PKG_CONFIG_PATH=\"$d${out}/lib/pkgconfig\" pkg-config --atleast-version=3.3.1 fcft\n"
273 "PKG_CONFIG_PATH=\"$d${out}/lib/pkgconfig\" pkg-config --max-version=3.99.99 fcft\n"
274 "echo FCFT-INSTALLED\n"))))))))