ttf-commissioner-hinted
LOW
maintainer gaougalos
0 votes
scanned 2026-10-26 15:45:54.756943
Why flagged
Uploaded within the last 14 days with 2 or fewer community votes — little peer review so far.
Triggered rules
Low
Few votes, recently uploaded
zero_votes_recent
Uploaded within the last 14 days with 2 or fewer community votes — little peer review so far.
PKGBUILD
1
# Maintainer: gaou-piou <i.am.piou@gmail.com>
2
pkgname=ttf-commissioner-hinted
3
_commver=1.012
4
_nfver=3.5.1
5
pkgver="${_commver}"
6
pkgrel=1
7
pkgdesc='Commissioner, a low-contrast humanist sans with Flare/Volume/Weight axes and a separate italic, re-hinted with ttfautohint for FreeType (optionally static / Nerd Fonts patched)'
8
arch=('any')
9
url='https://github.com/kosbarts/Commissioner'
10
license=('OFL-1.1')
11
makedepends=('python' 'python-fonttools' 'fontforge' 'parallel' 'ttfautohint')
12
options=(!strip)
13
provides=('ttf-commissioner')
14
conflicts=('ttf-commissioner' 'ttf-commissioner-variable' 'otf-commissioner')
15
source=(
16
"Commissioner-Roman.ttf::https://raw.githubusercontent.com/kosbarts/Commissioner/${_commver}/fonts/variable/Commissioner%5BFLAR%2CVOLM%2Cwght%5D.ttf"
17
"Commissioner-Italic.ttf::https://raw.githubusercontent.com/kosbarts/Commissioner/${_commver}/fonts/variable/Commissioner-Italic%5BFLAR%2CVOLM%2Cwght%5D.ttf"
18
"OFL.txt::https://raw.githubusercontent.com/kosbarts/Commissioner/${_commver}/OFL.txt"
19
"font-patcher-${_nfver}.zip::https://github.com/ryanoasis/nerd-fonts/releases/download/v${_nfver}/FontPatcher.zip"
20
"make_statics.py"
21
"set_gasp.py"
22
"measure.py"
23
"79-commissioner-families.conf"
24
"README.md"
25
"FEATURES.md"
26
)
27
noextract=("font-patcher-${_nfver}.zip")
28
sha256sums=('63ce7044195585bc41476193be7be0312e07ec6685921eb141c6eabfb0aee3b6'
29
'f14e729502d1bcb7873830791d1de58c366d403846eb680a4a1e1e293d6953f6'
30
'4a7d88c77b4bc39ff84f2e058ba8f015636c50ce33588a86d9da97defe519933'
31
'42bcb32145499a35732274c7fc48deb434ad0d2e0e118f98527c1479c6fa251a'
32
'eed086f8bce3b73ef93475f0706e0978c8070a3bddd47a1e1ce74223beffbe27'
33
'0b0464b268525569b5fa28b4735791efb22f22829ad178629776ae3ea82d7f99'
34
'373d859f527adcf80933f9741af0595e708f591bdde5f2f01896aebd7d488750'
35
'f09f7f4a7dbcc2b18c1bafeae56c5611d995066f0b1cbcfe84bb9890edc66d4f'
36
'7c40d541e807419c658a49839d1815b6965a5c14c287739560c85e936280ec65'
37
'3f86a8d1100a69364b1035189143950de9c9963b217184048808ae29e88a6129')
38
39
# ── What this package does ──────────────────────────────────────────────────
40
#
41
# Commissioner (Kostas Bartsokas) is a low-contrast humanist sans in three
42
# "voices" that are axis settings, not separate designs: the default grotesque,
43
# Flare (FLAR 100, glyphic serifs on the terminals) and Loud (FLAR 100 +
44
# VOLM 100, the same serifs as wedges). Verified against the designer's own
45
# static TTFs by rendering both at 200 px.
46
#
47
# The 1.012 release ships TWO variable fonts:
48
#
49
# Commissioner[FLAR,VOLM,wght].ttf roman, 9 instances, 3 axes
50
# Commissioner-Italic[FLAR,VOLM,wght].ttf italic, 9 instances, 3 axes
51
#
52
# The italic file is the roman sheared 12 degrees - measured pixel-identical
53
# to the third variable font in the repository,
54
# Commissioner[FLAR,VOLM,slnt,wght].ttf at slnt=-12, in every weight, in
55
# Latin, Greek and Cyrillic. That 4-axis file is NOT what this package
56
# installs, for one reason: it is stale. Google Fonts ships it, but it is
57
# still version 1.001 built with gftools 0.9.23 (1123 glyphs, 983 code points),
58
# it is not in the 1.012 release archive at all, and it is missing everything
59
# 1.012 added - tnum (tabular figures, also for + - = < > etc.), ss01 (the
60
# alternates of G with a bar), the arbitrary-fraction machinery (numr/dnom/
61
# frac), and the Greek OpenType fixes. The roman+italic pair is 1.012 in both
62
# files (1172 glyphs, 994 code points). What you lose is a continuous slnt
63
# axis; pango, Firefox and Chromium all ask for the italic style and get the
64
# italic file, so nothing in a desktop notices.
65
#
66
# Neither upstream file is hinted: no fpgm, no cvt, zero glyph instructions, a
67
# 7-byte prep. At hintstyle hintmedium FreeType therefore treats the face as
68
# hinted and runs no autohinter, i.e. GTK (manual mode, font-hinting medium)
69
# shows Commissioner completely unhinted. That is measurable: hintmedium on
70
# the unhinted font is identical to FT_LOAD_NO_HINTING, glyph for glyph.
71
# Upstream's 54 prebuilt statics, by contrast, ARE hinted (name table: "Version
72
# 1.012; ttfautohint (v1.8.4.16-eb64)") - so the 54-file AUR package
73
# ttf-commissioner is hinted and the variable font is not.
74
#
75
# Measured with FreeType 2.15.0, word "Hamburgefonstiv Αλφάβητο 0123".
76
# glued = i j ï ä ö ü ë ё ϊ ϋ ΐ ΰ with no pixel row under 25% coverage between
77
# mark and letter body, 10-20 px, 165 cases per weight, fewer is better.
78
# dev = summed coverage difference to an 8x supersampled unhinted render, % of
79
# its ink, best 1/8 px alignment, 12/13/14/16 px, lower is closer to the design:
80
#
81
# glued 100/400/700/900 dev 100/400/700/900
82
# unhinted (hintmedium) 0/ 6/ 38/129 4.1/ 2.7/ 1.5/ 1.0
83
# hintslight (autohinter) 0/ 0/ 6/ 34 12.5/11.7/ 8.4/ 7.0
84
# bytecode natural (n) 0/ 8/ 8/ 72 12.1/11.7/ 7.9/ 6.5 <- default
85
# bytecode balanced (q) 0/ 7/ 8/ 72 12.3/11.8/ 7.9/ 6.4
86
# bytecode sharp (s) 0/ 8/ 8/ 70 54.1/12.3/10.5/ 7.2
87
# bytecode light (n, 12-48) 0/ 8/ 8/ 72 12.1/11.7/ 7.9/ 6.5
88
# static, hinted per weight 0/ 5/ 8/129 11.0/11.2/ 8.0/ 6.4
89
# upstream's own statics 0/ 0/ 7/123 11.9/11.9/ 8.8/10.7
90
#
91
# Unlike Geologica, whose x-height grows 80 units across the axis and whose
92
# bytecode therefore snaps every weight to the Thin master's blue zones, this
93
# family barely moves: x-height 992 (Thin) to 1016 (Black) font units at 2000
94
# upem, cap height 1426 in every instance, ascender 1478-1500. So the two
95
# renderers land on the same pixel: the mean vertical shift of x, o and H is
96
# identical to the hundredth at 13, 14 and 16 px in all nine weights, and
97
# differs only at 12 px, by 0.4 px. What still differs is the dot metric:
98
# FreeType's light autohinter keeps the dot of i off the stem where ttfautohint
99
# cannot, because a one-pixel gap at 10-14 px in Black is below what bytecode
100
# hinting can express. That is why hintslight - the Arch default, and what the
101
# README recommends - remains the best single setting, and the bytecode this
102
# package adds is for the desktops that run hintmedium/hintfull, where the
103
# unhinted upstream font renders with no hinting at all.
104
#
105
# Note also that ttfautohint's --increase-x-height is silently ignored on a
106
# variable font: -x 0, -x 30 and -x 60 produce byte-identical rendering (still
107
# 8 glued at wght 400, 72 at 900). It does work on the statics, where it
108
# changes the Regular from 5 glued to 0 for 0.1 of deviation - see the
109
# "balanced" preset in the README's static section.
110
#
111
# FreeType only runs TrueType bytecode at hintstyle hintmedium/hintfull. This
112
# package ships (not enabled) /usr/share/fontconfig/conf.avail/80-$pkgname.conf
113
# which turns bytecode hinting on for the installed families only, and
114
# excludes Chromium/Electron by prgname (at device scale 1 they round every
115
# advance at hintmedium). It only affects clients that read hinting from
116
# fontconfig (Firefox, Chromium, Qt, kitty/foot): GTK3/GTK4 in manual mode take
117
# the hint style from gsettings and ignore fontconfig's hintstyle. See
118
# README.md for the recommended desktop settings, which for Commissioner are
119
# hintslight plus stem darkening (+39% ink on Thin, +23% on ExtraLight, +10%
120
# on Regular - the light end of this family needs it).
121
#
122
# ── Build-time options ───────────────────────────────────────────────────────
123
#
124
# Run from a terminal, prepare() asks for COMM_VARIANT, NERD_PATCH, HINTING and
125
# HINT_PRESET (Enter keeps the default). Any of them already set in the
126
# environment is not asked. Without a TTY nothing is asked and the
127
# environment / defaults below apply. Answers are saved to $srcdir/.build_opts.
128
#
129
# COMM_VARIANT variable|static (default: variable)
130
# variable the roman and the italic variable font: every axis
131
# stays usable
132
# static one TTF per weight (and italic), cut with the
133
# fontTools instancer; each one is hinted on its own,
134
# so its blue zones match that exact weight
135
#
136
# STATIC_WEIGHTS "100 ... 900" wght values to cut (default: all nine:
137
# 100 200 300 400 500 600 700 800 900)
138
# STATIC_ITALIC true/false also cut the italic, default true
139
# STATIC_VOICES "default flare loud" voices to cut, default "default":
140
# default FLAR 0, VOLM 0 family "Commissioner"
141
# flare FLAR 100, VOLM 0 family "Commissioner Flare"
142
# loud FLAR 100, VOLM 100 family "Commissioner Loud"
143
# All three = 54 files, the same set the designer ships, but
144
# hinted per weight instead of all with one set of flags.
145
#
146
# NERD_PATCH true/false Patch with Nerd Fonts glyphs (default: false).
147
# font-patcher drops variable axes, so this forces
148
# COMM_VARIANT=static. Family becomes
149
# "Commissioner Nerd Font (Mono|Propo)".
150
#
151
# HINTING true/false Hint with ttfautohint (default: true). false installs
152
# the fonts as upstream ships them: no hinting at all
153
# at hintmedium, the autohinter at hintslight.
154
#
155
# HINT_PRESET name Named bundle of ttfautohint flags (default: natural)
156
# ("Linux" = the stem letter FreeType v40 actually uses)
157
# natural stem=nnn (Linux: n) range 8-48 x-height 0 (closest to
158
# the outline; best
159
# measured preset)
160
# balanced stem=qsq (Linux: q) range 8-48 x-height 14 (ttfautohint
161
# defaults; on the
162
# VF the x-height
163
# part is ignored,
164
# on statics it
165
# opens the dots at
166
# weight 400)
167
# sharp stem=sss (Linux: s) range 6-48 x-height 14 (highest
168
# contrast, distorts
169
# Thin: dev 54.1)
170
# light stem=nnn (Linux: n) range 12-48 x-height 0 (HiDPI; <12px
171
# left alone)
172
# custom use the individual HINT_* vars below verbatim
173
#
174
# Individual ttfautohint knobs (override the preset; empty = use preset value):
175
# HINT_MODE nnn|qqq|qsq|sss 3-char --stem-width-mode (grayscale/GDI/DW)
176
# On Linux only the 3rd letter has an effect.
177
# HINT_RANGE_MIN N --hinting-range-min
178
# HINT_RANGE_MAX N --hinting-range-max
179
# HINT_LIMIT N --hinting-limit (default: HINT_RANGE_MAX; 0 = no limit)
180
# HINT_XHEIGHT N --increase-x-height (0 = disable; no effect on the
181
# variable fonts, see above)
182
# HINT_XSNAP_EXC STR --x-height-snapping-exceptions (unset = none)
183
# HINT_DEFAULT_SCRIPT STR --default-script (default: latn)
184
# HINT_FALLBACK_SCRIPT STR --fallback-script (default: latn; Nerd-patched
185
# builds always use none plus --fallback-scaling)
186
# HINT_WINCOMPAT true/false --windows-compatibility (two extra blue zones
187
# for GDI ClearType; Windows/Wine only)
188
# HINT_TTFA_TABLE true/false --ttfa-table (default: false)
189
# HINT_FAMILY_SUFFIX STR -F/--family-suffix, e.g. " Hinted"
190
#
191
# HINT_INTEGER_PPEM true/false Set head.flags bit 3 ("ppem is always an
192
# integer"), which ttfautohint leaves clear and fontbakery
193
# wants set for hinted fonts (default: false). On Linux
194
# the flag does not change the ppem FreeType picks
195
# (measured: 11 px at 8 pt / 96 dpi with the bit clear
196
# and set); it only feeds the hinter's integer
197
# assumption, which moves ink by up to 3.5%. Enable it
198
# to satisfy fontbakery and match Windows GDI.
199
#
200
# GASP_MODE keep|sized|smooth|gridfit (default: keep; FreeType ignores gasp,
201
# Windows uses it)
202
# keep leave the gasp table as ttfautohint wrote it
203
# sized MS strategy: <=8 grayscale, 9-16 gridfit, 17+ both
204
# smooth all sizes anti-aliased ({0xFFFF: 15})
205
# gridfit all sizes gridfit+grayscale ({0xFFFF: 3})
206
#
207
# Examples:
208
# makepkg -si (asks, when run from a terminal)
209
# makepkg -si </dev/null (never asks: defaults only)
210
# COMM_VARIANT=static makepkg -si
211
# COMM_VARIANT=static STATIC_VOICES="default flare loud" makepkg -si
212
# COMM_VARIANT=static STATIC_ITALIC=false STATIC_VOICES=flare makepkg -si
213
# HINT_PRESET=balanced makepkg -si
214
# NERD_PATCH=true STATIC_WEIGHTS="400 500 700" makepkg -si
215
# ────────────────────────────────────────────────────────────────────────────
216
217
_opt_keys=(COMM_VARIANT STATIC_WEIGHTS STATIC_ITALIC STATIC_VOICES NERD_PATCH
218
HINTING HINT_PRESET HINT_MODE HINT_RANGE_MIN HINT_RANGE_MAX
219
HINT_LIMIT HINT_XHEIGHT HINT_XSNAP_EXC HINT_DEFAULT_SCRIPT
220
HINT_FALLBACK_SCRIPT HINT_WINCOMPAT HINT_TTFA_TABLE
221
HINT_FAMILY_SUFFIX HINT_INTEGER_PPEM GASP_MODE)
222
223
# _ask_yn QUESTION DEFAULT(y|n) -> sets _yn to true/false
224
_ask_yn() {
225
local _ans
226
read -r -p " $1? [$2] " _ans || _ans=""
227
[[ "${_ans:-$2}" =~ ^[yY] ]] && _yn=true || _yn=false
228
}
229
230
# _ask_choice TITLE DEFAULT KEY:description ... -> sets _choice to a KEY.
231
# Enter (or EOF) keeps DEFAULT; anything else must be a listed number.
232
_ask_choice() {
233
local _title="$1" _default="$2"; shift 2
234
local -a _items=("$@")
235
local _i _ans
236
echo " $_title:"
237
for _i in "${!_items[@]}"; do
238
printf " %d) %-12s %s%s\n" "$((_i + 1))" "${_items[$_i]%%:*}" "${_items[$_i]#*:}" \
239
"$([[ "${_items[$_i]%%:*}" == "$_default" ]] && echo " (default)")"
240
done
241
while :; do
242
read -r -p " > " _ans || _ans=""
243
if [[ -z "$_ans" ]]; then _choice="$_default"; return; fi
244
if [[ "$_ans" =~ ^[0-9]+$ ]] && (( _ans >= 1 && _ans <= ${#_items[@]} )); then
245
_choice="${_items[$((_ans - 1))]%%:*}"; return
246
fi
247
echo " Enter 1-${#_items[@]}, or press Enter for $_default."
248
done
249
}
250
251
# Called once, from prepare(): ask for whatever the environment has not set,
252
# but only on a terminal, then save every set option to .build_opts.
253
_prompt_options() {
254
if [[ -t 0 ]]; then
255
echo ""
256
echo " $pkgname: build options (Enter = default)"
257
echo ""
258
if [[ -z "${NERD_PATCH:-}" ]]; then
259
_ask_yn "Patch with Nerd Fonts glyphs (forces static fonts)" n; NERD_PATCH=$_yn
260
fi
261
if [[ "$NERD_PATCH" != true && -z "${COMM_VARIANT:-}" ]]; then
262
_ask_choice "Font set" variable \
263
"variable:roman + italic font, all axes (recommended)" \
264
"static:one file per weight/italic/voice, hinted per weight"
265
COMM_VARIANT=$_choice
266
fi
267
if [[ -z "${HINTING:-}" ]]; then
268
_ask_yn "Hint with ttfautohint (n = unhinted, as upstream)" y; HINTING=$_yn
269
fi
270
if [[ "$HINTING" == true && -z "${HINT_PRESET:-}" ]]; then
271
_ask_choice "Hinting preset" natural \
272
"natural:stem n, 8-48px, no x-height boost (measured best)" \
273
"balanced:stem q, 8-48px (ttfautohint defaults, opens the dots)" \
274
"sharp:stem s, 6-48px (highest contrast, distorts Thin)" \
275
"light:stem n, 12-48px, no x-height boost (HiDPI)" \
276
"custom:use the HINT_* environment variables"
277
HINT_PRESET=$_choice
278
fi
279
echo ""
280
else
281
echo "==> Non-interactive build (no TTY): using environment variables / defaults."
282
fi
283
284
local _k
285
: > "$srcdir/.build_opts"
286
for _k in "${_opt_keys[@]}"; do
287
# "set" rather than non-empty: HINT_XSNAP_EXC="" (no exceptions) differs
288
# from HINT_XSNAP_EXC unset (ttfautohint's default).
289
if [[ "${!_k+set}" == set ]]; then
290
printf '%s=%s\n' "$_k" "${!_k}" >> "$srcdir/.build_opts"
291
fi
292
done
293
}
294
295
# Load .build_opts (whitelisted keys only), then apply defaults and validate.
296
# makepkg runs build() and package() in separate shells, so both call this.
297
_resolve_options() {
298
local _k _v
299
if [[ ! -f "$srcdir/.build_opts" ]]; then
300
echo "Error: $srcdir/.build_opts missing; run a full makepkg (prepare() writes it)." >&2
301
exit 1
302
fi
303
while IFS='=' read -r _k _v; do
304
if [[ " ${_opt_keys[*]} " == *" $_k "* ]]; then
305
printf -v "$_k" '%s' "$_v"
306
fi
307
done < "$srcdir/.build_opts"
308
309
COMM_VARIANT="${COMM_VARIANT:-variable}"
310
STATIC_WEIGHTS="${STATIC_WEIGHTS:-100 200 300 400 500 600 700 800 900}"
311
STATIC_ITALIC="${STATIC_ITALIC:-true}"
312
STATIC_VOICES="${STATIC_VOICES:-default}"
313
NERD_PATCH="${NERD_PATCH:-false}"
314
HINTING="${HINTING:-true}"
315
HINT_PRESET="${HINT_PRESET:-natural}"
316
GASP_MODE="${GASP_MODE:-keep}"
317
HINT_WINCOMPAT="${HINT_WINCOMPAT:-false}"
318
HINT_TTFA_TABLE="${HINT_TTFA_TABLE:-false}"
319
HINT_INTEGER_PPEM="${HINT_INTEGER_PPEM:-false}"
320
HINT_DEFAULT_SCRIPT="${HINT_DEFAULT_SCRIPT:-latn}"
321
HINT_FALLBACK_SCRIPT="${HINT_FALLBACK_SCRIPT:-latn}"
322
323
case "$COMM_VARIANT" in
324
variable|static) ;;
325
*) echo "Error: COMM_VARIANT='$COMM_VARIANT' invalid (variable|static)." >&2; exit 1 ;;
326
esac
327
for _v in STATIC_ITALIC NERD_PATCH HINTING HINT_WINCOMPAT HINT_TTFA_TABLE HINT_INTEGER_PPEM; do
328
case "${!_v}" in
329
true|false) ;;
330
*) echo "Error: $_v='${!_v}' must be true or false." >&2; exit 1 ;;
331
esac
332
done
333
# font-patcher drops fvar/gvar, so patching always works on statics.
334
if [[ "$NERD_PATCH" == true && "$COMM_VARIANT" == variable ]]; then
335
echo "==> Note: the variable font cannot be Nerd-patched; using COMM_VARIANT=static."
336
COMM_VARIANT=static
337
fi
338
for _v in $STATIC_WEIGHTS; do
339
case "$_v" in
340
100|200|300|400|500|600|700|800|900) ;;
341
*) echo "Error: STATIC_WEIGHTS: '$_v' is not 100, 200, ... 900." >&2; exit 1 ;;
342
esac
343
done
344
local _voice _seen=" "
345
for _voice in $STATIC_VOICES; do
346
case "$_voice" in
347
default|flare|loud) ;;
348
*) echo "Error: STATIC_VOICES: '$_voice' is not default, flare or loud." >&2; exit 1 ;;
349
esac
350
if [[ "$_seen" == *" $_voice "* ]]; then
351
echo "Error: STATIC_VOICES: '$_voice' listed twice." >&2; exit 1
352
fi
353
_seen+="$_voice "
354
done
355
356
local _m _lo _hi _xh
357
case "$HINT_PRESET" in
358
natural) _m=nnn; _lo=8; _hi=48; _xh=0 ;;
359
balanced) _m=qsq; _lo=8; _hi=48; _xh=14 ;;
360
sharp) _m=sss; _lo=6; _hi=48; _xh=14 ;;
361
light) _m=nnn; _lo=12; _hi=48; _xh=0 ;;
362
custom) _m=nnn; _lo=8; _hi=48; _xh=0 ;;
363
*)
364
echo "Error: unknown HINT_PRESET='$HINT_PRESET'." >&2
365
echo " Valid: natural balanced sharp light custom" >&2
366
exit 1 ;;
367
esac
368
# A preset never overrides a value the user set explicitly.
369
HINT_MODE="${HINT_MODE:-$_m}"
370
HINT_RANGE_MIN="${HINT_RANGE_MIN:-$_lo}"
371
HINT_RANGE_MAX="${HINT_RANGE_MAX:-$_hi}"
372
HINT_LIMIT="${HINT_LIMIT:-$HINT_RANGE_MAX}"
373
HINT_XHEIGHT="${HINT_XHEIGHT:-$_xh}"
374
375
if [[ ! "$HINT_MODE" =~ ^[nqs]{3}$ ]]; then
376
echo "Error: HINT_MODE='$HINT_MODE' must be 3 chars from n/q/s (e.g. nnn)." >&2
377
exit 1
378
fi
379
for _v in HINT_RANGE_MIN HINT_RANGE_MAX HINT_LIMIT HINT_XHEIGHT; do
380
if [[ ! "${!_v}" =~ ^[0-9]+$ ]]; then
381
echo "Error: $_v='${!_v}' must be a non-negative integer." >&2; exit 1
382
fi
383
done
384
if (( HINT_RANGE_MIN > HINT_RANGE_MAX )); then
385
echo "Error: HINT_RANGE_MIN ($HINT_RANGE_MIN) > HINT_RANGE_MAX ($HINT_RANGE_MAX)." >&2
386
exit 1
387
fi
388
for _v in HINT_DEFAULT_SCRIPT HINT_FALLBACK_SCRIPT; do
389
if [[ ! "${!_v}" =~ ^[a-z0-9]{3,4}$ ]]; then
390
echo "Error: $_v='${!_v}' must be a script tag (e.g. latn, none)." >&2; exit 1
391
fi
392
done
393
case "$GASP_MODE" in
394
keep|sized|smooth|gridfit) ;;
395
*) echo "Error: GASP_MODE='$GASP_MODE' invalid (keep|sized|smooth|gridfit)." >&2; exit 1 ;;
396
esac
397
}
398
399
prepare() {
400
_prompt_options
401
_resolve_options # validate now, so a typo fails before the long steps
402
403
rm -rf "$srcdir/font-patcher"
404
if [[ "$NERD_PATCH" == true ]]; then
405
mkdir -p "$srcdir/font-patcher"
406
bsdtar xf "$srcdir/font-patcher-${_nfver}.zip" -C "$srcdir/font-patcher"
407
fi
408
}
409
410
build() {
411
_resolve_options
412
413
echo "==> Options: variant=$COMM_VARIANT nerd=$NERD_PATCH hinting=$HINTING"
414
if [[ "$COMM_VARIANT" == static ]]; then
415
echo "==> Statics: weights='$STATIC_WEIGHTS' italic=$STATIC_ITALIC voices='$STATIC_VOICES'"
416
fi
417
if [[ "$HINTING" == true ]]; then
418
echo "==> ttfautohint: preset=$HINT_PRESET stem=$HINT_MODE" \
419
"range=$HINT_RANGE_MIN-$HINT_RANGE_MAX limit=$HINT_LIMIT x-height=$HINT_XHEIGHT" \
420
"x-snap-exc='${HINT_XSNAP_EXC-<default>}' gasp=$GASP_MODE integer-ppem=$HINT_INTEGER_PPEM" \
421
"ttfa-table=$HINT_TTFA_TABLE${HINT_FAMILY_SUFFIX:+ suffix='$HINT_FAMILY_SUFFIX'}"
422
fi
423
424
rm -rf "$srcdir/selected" "$srcdir/patched" "$srcdir/hinted"
425
mkdir -p "$srcdir/selected" "$srcdir/patched" "$srcdir/hinted"
426
427
# ── Step 1: select ──────────────────────────────────────────────────────────
428
if [[ "$COMM_VARIANT" == variable ]]; then
429
# A plain filename: brackets in file names upset some tools and globs.
430
install -m644 "$srcdir/Commissioner-Roman.ttf" "$srcdir/Commissioner-Italic.ttf" \
431
"$srcdir/selected/"
432
else
433
echo "==> Cutting static instances..."
434
local -a _jobs=()
435
local _src _w _voice _flar _volm
436
for _src in Roman Italic; do
437
[[ "$_src" == Italic && "$STATIC_ITALIC" != true ]] && continue
438
for _w in $STATIC_WEIGHTS; do
439
for _voice in $STATIC_VOICES; do
440
case "$_voice" in
441
default) _flar=0; _volm=0 ;;
442
flare) _flar=100; _volm=0 ;;
443
loud) _flar=100; _volm=100 ;;
444
esac
445
_jobs+=("$_w $_flar $_volm $_src")
446
done
447
done
448
done
449
printf '%s\n' "${_jobs[@]}" | parallel --will-cite -j"$(nproc)" --colsep ' ' \
450
python "$srcdir/make_statics.py" --wght {1} --flar {2} --volm {3} \
451
-o "$srcdir/selected" "$srcdir/Commissioner-{4}.ttf"
452
fi
453
local _count
454
_count=$(find "$srcdir/selected" -name '*.ttf' | wc -l)
455
echo "==> Selected $_count font file(s)."
456
if (( _count == 0 )); then
457
echo "Error: no fonts produced." >&2; exit 1
458
fi
459
460
local _hintsrc="$srcdir/selected"
461
462
# ── Step 2: Nerd Fonts patching ─────────────────────────────────────────────
463
# Patch first, hint afterwards: font-patcher does not keep hint bytecode, and
464
# ttfautohint then sees the final glyph set including the icons.
465
if [[ "$NERD_PATCH" == true ]]; then
466
echo "==> Patching with Nerd Fonts glyphs (this takes a while)..."
467
find "$srcdir/selected" -type f | sort | \
468
parallel --will-cite -j"$(nproc)" python "$srcdir/font-patcher/font-patcher" \
469
--variable-width-glyphs -q -c {} -out "$srcdir/patched" '>/dev/null'
470
_count=$(find "$srcdir/patched" -maxdepth 1 -name '*.ttf' | wc -l)
471
if (( _count == 0 )); then
472
echo "Error: font-patcher produced no output. See messages above." >&2; exit 1
473
fi
474
echo "==> Patched $_count font(s)."
475
_hintsrc="$srcdir/patched"
476
fi
477
478
if [[ "$HINTING" != true ]]; then
479
return 0
480
fi
481
482
# ── Step 3: ttfautohint ─────────────────────────────────────────────────────
483
# On a variable font ttfautohint keeps fvar/gvar/STAT/MVAR and every named
484
# instance, but its blue zones come from the default master (Thin) and its
485
# --increase-x-height is ignored. On a static it hints that exact instance.
486
local -a _ta_opts=(
487
--hinting-range-min="$HINT_RANGE_MIN"
488
--hinting-range-max="$HINT_RANGE_MAX"
489
--hinting-limit="$HINT_LIMIT"
490
--increase-x-height="$HINT_XHEIGHT"
491
--stem-width-mode="$HINT_MODE"
492
--default-script="$HINT_DEFAULT_SCRIPT"
493
--ignore-restrictions
494
)
495
if [[ "$NERD_PATCH" == true ]]; then
496
# Icons would otherwise get Latin blue zones: scale them instead.
497
_ta_opts+=(--fallback-script=none --fallback-scaling)
498
else
499
_ta_opts+=(--fallback-script="$HINT_FALLBACK_SCRIPT")
500
fi
501
# Honour an explicit empty HINT_XSNAP_EXC (= no exceptions); omit if unset.
502
if [[ "${HINT_XSNAP_EXC+set}" == set ]]; then
503
_ta_opts+=(--x-height-snapping-exceptions="$HINT_XSNAP_EXC")
504
fi
505
if [[ "$HINT_WINCOMPAT" == true ]]; then _ta_opts+=(--windows-compatibility); fi
506
if [[ -n "${HINT_FAMILY_SUFFIX:-}" ]]; then _ta_opts+=(--family-suffix="$HINT_FAMILY_SUFFIX"); fi
507
if [[ "$HINT_TTFA_TABLE" == true ]]; then _ta_opts+=(--ttfa-table); else _ta_opts+=(--no-info); fi
508
509
# Serial on purpose: GNU parallel rebuilds the command line from its
510
# arguments, so a knob whose value contains a space (HINT_FAMILY_SUFFIX="
511
# Hinted", HINT_XSNAP_EXC="10-12 14") is split and ttfautohint prints its
512
# usage and fails. Hinting one font takes well under a second.
513
echo "==> Hinting with ttfautohint..."
514
local _f
515
for _f in "$_hintsrc"/*.ttf; do
516
ttfautohint "${_ta_opts[@]}" "$_f" "$srcdir/hinted/${_f##*/}"
517
done
518
519
if [[ "$HINT_INTEGER_PPEM" == true ]]; then
520
echo "==> Setting head.flags bit 3 (integer ppem)..."
521
python - "$srcdir/hinted/"*.ttf <<'PY'
522
import sys
523
from fontTools.ttLib import TTFont
524
for path in sys.argv[1:]:
525
font = TTFont(path)
526
font["head"].flags |= 1 << 3
527
font.save(path)
528
PY
529
fi
530
531
if [[ "$GASP_MODE" != keep ]]; then
532
echo "==> Applying gasp mode '$GASP_MODE'..."
533
python "$srcdir/set_gasp.py" "$GASP_MODE" "$srcdir/hinted/"*.ttf
534
fi
535
}
536
537
package() {
538
_resolve_options
539
540
local _out="$srcdir/selected"
541
if [[ "$HINTING" == true ]]; then
542
_out="$srcdir/hinted"
543
elif [[ "$NERD_PATCH" == true ]]; then
544
_out="$srcdir/patched"
545
fi
546
if ! compgen -G "$_out/*.ttf" >/dev/null; then
547
echo "Error: no fonts to install in $_out." >&2; exit 1
548
fi
549
local _fontdir="$pkgdir/usr/share/fonts/commissioner"
550
install -Dm644 -t "$_fontdir" "$_out/"*.ttf
551
install -Dm644 "$srcdir/OFL.txt" "$pkgdir/usr/share/licenses/$pkgname/OFL.txt"
552
553
local _avail="$pkgdir/usr/share/fontconfig/conf.avail"
554
install -d "$_avail"
555
556
# Family names for the FLAR/VOLM voices. Variable build only: the statics
557
# have no axes, and with STATIC_VOICES="default flare loud" they are already
558
# "Commissioner", "Commissioner Flare" and "Commissioner Loud".
559
if [[ "$COMM_VARIANT" == variable ]]; then
560
install -Dm644 -t "$_avail" "$srcdir/79-commissioner-families.conf"
561
fi
562
563
# Opt-in rendering tweak (see the header): bytecode hinting for the families
564
# this build installed. Shipped in conf.avail and deliberately not enabled.
565
# The family list is read from the fonts, so Nerd Fonts names and family
566
# suffixes are covered.
567
if [[ "$HINTING" == true ]]; then
568
local _fam _conf="$_avail/80-$pkgname.conf"
569
local _chromium_prgnames=(chrome chromium brave helium electron code slack signal-desktop vesktop)
570
local _prg _prgtests=""
571
for _prg in "${_chromium_prgnames[@]}"; do
572
_prgtests+=" <test qual=\"all\" name=\"prgname\" compare=\"not_eq\"><string>$_prg</string></test>"$'\n'
573
done
574
{
575
echo '<?xml version="1.0" encoding="UTF-8"?>'
576
echo '<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">'
577
echo "<!-- Installed by $pkgname. Use the ttfautohint bytecode hints in these"
578
echo " fonts rather than FreeType's light autohinter. hintmedium, not"
579
echo " hintfull: same rendering in cairo/pango, and Blink keeps subpixel"
580
echo " positioning at hintmedium but drops it at hintfull."
581
echo " Chromium and Electron apps are excluded: at device scale 1 they use"
582
echo " linear advances only for hintslight/none, so hintmedium would round"
583
echo " every glyph advance to a whole pixel (uneven letter gaps)."
584
echo ""
585
echo " Commissioner does not need this file as badly as Geologica does:"
586
echo " its x-height barely moves with the weight, so the bytecode and the"
587
echo " autohinter snap to the same pixel at 13-16 px. What the bytecode"
588
echo " cannot do is keep the dot of i off the stem at 10-14 px in Bold and"
589
echo " Black (72 glued cases at wght 900 against the autohinter's 34), and"
590
echo " that is the argument for staying on hintstyle hintslight. See"
591
echo " README.md section 2. -->"
592
echo '<fontconfig>'
593
# One <match> per family: several <test>s in one <match> are ANDed, so
594
# the prgname tests exclude every listed app. qual="all" makes a test
595
# pass when prgname is unset. prgname is the executable's basename.
596
while IFS= read -r _fam; do
597
_fam="${_fam//&/&}"; _fam="${_fam//</<}"
598
cat <<EOF
599
<match target="font">
600
<test name="family" compare="eq"><string>$_fam</string></test>
601
${_prgtests%$'\n'}
602
<edit name="hinting" mode="assign"><bool>true</bool></edit>
603
<edit name="autohint" mode="assign"><bool>false</bool></edit>
604
<edit name="hintstyle" mode="assign"><const>hintmedium</const></edit>
605
</match>
606
EOF
607
done < <(python - "$_fontdir/"*.ttf <<'PY' | sort -u
608
import sys
609
from fontTools.ttLib import TTFont
610
for path in sys.argv[1:]:
611
name = TTFont(path, lazy=True)["name"]
612
print(name.getDebugName(16) or name.getDebugName(1))
613
PY
614
)
615
echo '</fontconfig>'
616
} > "$_conf"
617
chmod 644 "$_conf"
618
fi
619
620
install -Dm644 -t "$pkgdir/usr/share/doc/$pkgname" "$srcdir/README.md" "$srcdir/FEATURES.md"
621
}
622
Scan history
| Scanned at (UTC) | Severity | Rules |
|---|---|---|
| 2026-10-26 15:45:54 | Low | 1 |