ShirokumaGen Term (font resource for extensions)Extension ID: A general-purpose font resource extension that supplies the ShirokumaGen Term
font to other extensions. It has no UI and no commands of its own
( ShirokumaGen Term is a modified version of HackGen Console (白源
Console, v2.10.0), refitted for terminal use: the symbols a terminal counts as
one column wide are brought under the width at which a renderer squashes them
sideways. Upstream draws those symbols on the full-width advance 1080, and
the refit sets the advance of the selected glyphs to the one-cell width 540
— 1656 glyphs in the Regular face and 1621 in the Bold, measured on the shipped
files. Ink wider than 1.15 cells is scaled down to exactly that width, and
any ink that reaches past its cell is centred on the cell, so the overhang is the
same on both sides and never exceeds 41 units of a 540-unit cell (measured; the
ratio puts it at 40.5 and integer rounding accounts for the rest — about 7.6%).
A symbol may therefore touch its neighbour slightly; what it never does is
reach the squashing threshold. The ratio itself — what it means, its 1.5 ceiling
and why 1.15 — is defined once, in The two axes are scaled by different factors: the refit takes the height to
Bundled font
Two faces of one family, each converted from the corresponding upstream static
face. Each Which upstream variant, and why it matters. HackGen publishes four families
per release; this package converts See Scope limitation: it does not work in
|
| upstream archive | sha256 (measured on the download) |
|---|---|
https://github.com/yuru7/HackGen/releases/download/v2.10.0/HackGen_v2.10.0.zip |
ed182e2a4b95792d94dea7932f6b45280b5ae353651be249d5f6b7867b788db7 |
The archive itself is not committed. It holds 8 TTFs and nothing else — the
whole entry listing is 8 .ttf entries plus the one directory, measured with
python3 -m zipfile -l (this host has no unzip/7z/bsdtar), and no
license and no README entry. The two entries taken out of it are the Console
variant's Regular and Bold; their hashes are the values manifest.json carries
as srcSha256, and run.sh re-checks both against the --src-dir it is given
before it writes a single byte.
| archive entry | sha256 (entry, on extraction) |
|---|---|
HackGen_v2.10.0/HackGenConsole-Regular.ttf |
72f550f90b49be633a9661f153b8c8398272785d9beda53dd5c400b61412a3ec |
HackGen_v2.10.0/HackGenConsole-Bold.ttf |
268a3fb847765aa12779e6021329e5aa33a45a6cb4c98ed0d40ee9d201d5d84a |
Upstream project: HackGen (白源) https://github.com/yuru7/HackGen.
License documents (not in the archive — taken from the upstream repository)
Because the release archive carries no license file of its own, the primary
documents come from the upstream repository at its v2.10.0 tag, by the
route font-packages/README.md describes for exactly this case. Their
provenance is recorded like the TTFs': two independently measured columns, the
left one taken on fetch (before placement), the right one off the placed file.
| source URL | sha256 (fetched) | placed at | sha256 (placed) |
|---|---|---|---|
https://raw.githubusercontent.com/yuru7/HackGen/v2.10.0/LICENSE |
9bfc3b3826a9c71042b87fda90a19cda4918894cdbd449cab163b7a7c93c5aa7 |
licenses/LICENSE_HackGen |
9bfc3b3826a9c71042b87fda90a19cda4918894cdbd449cab163b7a7c93c5aa7 |
https://raw.githubusercontent.com/yuru7/HackGen/v2.10.0/source/LICENSE_GenJyuuGothic |
68b83948ea44760eaf1cefc59f0ce2a381bb0e307508e8f0e5e8df529ee04170 |
licenses/LICENSE_GenJyuuGothic |
68b83948ea44760eaf1cefc59f0ce2a381bb0e307508e8f0e5e8df529ee04170 |
https://raw.githubusercontent.com/yuru7/HackGen/v2.10.0/source/LICENSE_Hack |
1f61bb7c790c59b4b0ecdf304628b94e42ae4c8020094a8c3da73381ab212623 |
licenses/LICENSE_Hack |
1f61bb7c790c59b4b0ecdf304628b94e42ae4c8020094a8c3da73381ab212623 |
https://raw.githubusercontent.com/yuru7/HackGen/v2.10.0/source/LICENSE_NerdFonts |
1f6ad4edae6479aaace3112ede5279a23284ae54b2a34db66357aef5f64df160 |
licenses/LICENSE_NerdFonts |
1f6ad4edae6479aaace3112ede5279a23284ae54b2a34db66357aef5f64df160 |
(the same fetched LICENSE) |
9bfc3b3826a9c71042b87fda90a19cda4918894cdbd449cab163b7a7c93c5aa7 |
OFL 1.1 body quoted verbatim in LICENSE.txt |
— |
The root LICENSE appears twice because it is both placed as a file and
quoted: the placed copy carries the two measured columns, while the
quotation into LICENSE.txt has no placed file of its own to hash and takes the
— the convention prescribes. The quotation was checked byte-identical to the
fetched document, which is the claim the right-hand column would have carried.
licenses/LICENSE_NerdFonts is shipped even though this is the non-NF
variant, and the reason is measured rather than assumed: the shipped faces'
name table nameID 0 credits Nerd Fonts (Copyright (c) 2014, Ryan L McIntyre),
carried through from upstream unchanged, so the document the credit points at
travels with it. That is a statement about the metadata, not about the glyphs —
the icon set is what the NF variant adds, and this one carries 7 private-use code
points in total.
2. Conversion (what turned input into output)
Pipeline:
scripts/font-fit/run.shin this repository, driven byscripts/font-fit/manifest.json(this package is one entry in it; membership of that file is what makes a package a converted one).fit.pydoes the refit,verify.pygates the result, anddump-widths.mjsproduces the cell-width table from@xterm/addon-unicode11's own provider — a hand-written width table is forbidden.instance.pydid not run for this package: both upstream faces are already static (fvarabsent, measured), so there was no axis to pin. Adding this package required exactly one new entry inmanifest.jsonand no change to any script —run.sh,fit.pyandverify.pyknow no typeface name, which is the property design #271 R5 was aiming at and this package is the first outside case to exercise it.Which converter, identified by content rather than by commit, pinned by the sha256 of each file that determines the output:
pipeline file sha256 scripts/font-fit/fit.pyc634881d418a59d68f1892db4d35f4fc66780a71cff277448cbef91479470bf7scripts/font-fit/instance.py86240ac88d1dfa4527ebaa1e7e97caf6c656e97cd04833d12b21149d2fefe2afscripts/font-fit/verify.py5c7d58d6a8ff4e4d248990c4bc39bcf379faee2fec9303ea3966c6ce1c5d9cf5scripts/font-fit/dump-widths.mjs4813198fba79edb3d3086aa07b8808183a362406b1a1da9c7d614c5b90633a03scripts/font-fit/run.sh4966ccca260ded4b1d7aa94fa71bc9802956363e9a646c788166ee530ab8c55ascripts/font-fit/manifest.json0fed27ae2614fd0cbe570cde156bb4ed5ff32fe7a0a7c814b97d71acaf49629binstance.pyis listed because it is part of the pipeline whose content determines what a run produces, not because it ran here. A commit SHA would not do: a git SHA is content-addressed, so a file cannot name the commit that contains it, and recording one forces either a two-commit split (breaking the coupling the sha256 binding depends on) or a placeholder that ships silently when nobody substitutes it. Themanifest.jsonrow is also why adding this package edits the other converted packages' READMEs in the same commit: the manifest is one of the hashed files, so a new entry invalidates every row that named the old one.Measured tool versions (printed by
run.shat the start of the run, not copied from documentation):tool version fontTools 4.63.0Python 3.14.4@xterm/addon-unicode110.10.0-beta.288Fixed environment:
PYTHONHASHSEED=0,LC_ALL=C.UTF-8, and fontTools saved withrecalcTimestamp=False(a recalculatedhead.modifiedwould make every run produce different bytes and the output hashes below meaningless).What the run changed, per face — measured, and reported by
run.sh:output face glyphs refitted of which moved only / scaled already fitting advances changed Regular 1723 956 / 767 1193 1656 (all 1080 → 540) Bold 1694 888 / 806 1201 1621 (all 1080 → 540) The advance column is the tier that upstream's design makes necessary here: HackGen Console draws these ambiguous-width symbols on the full-width advance, so a terminal counting them as one column hands them a single cell. Setting the advance to
Ais what settles that, and it is a change to the selected glyphs only — every other advance in the file is untouched (measured by diffing the shipped files against the upstream inputs: 1656 and 1621 changes, and no other).The
fitRatiothe run was driven by: 1.15 (manifest.json's top-level default; this package declares no override). Ink wider thanA × 1.15— 621 units of the 540-unit cell — is scaled down to it. The value's meaning and its 1.5 ceiling are defined inscripts/font-fit/manifest.json'snoteand not restated here.The
vstretchthe run was driven by: 1.3 (manifest.json's top-level default; this package declares no override), with the vertical limit measured from each face rather than declared — the top and bottom edges of 漢 (U+6F22),y = −72…856in the Regular face and−80…866in the Bold, asrun.shprinted them. The clamp bound the stretch on 987 of the 1723 refitted glyphs in the Regular face and 919 of 1694 in the Bold; of those, 595 and 579 respectively had no room left at equal scaling and were held at the clamp's 1.0 floor — not stretched, which is not the same as not moved. The three existing packages can say "unmoved" because their baseline is their own previous release (#291 already applied the horizontal refit); this package has no previous release, so its baseline is upstream, and against upstream only 8 of those 595 (Regular) and 11 of those 579 (Bold) are unchanged to the unit. The rest moved in the same pass: 133 and 152 of them were scaled down in both directions (s < 1), and the remainder kepts = 1but were re-centred and/or had their advance set toA(442 and 404 with the advance changed, 12 and 12 with it unchanged). Measured by re-derivingfit.py's selection withverify.py'sselected_glyphson the upstream face and comparing each selected glyph's point sequence (DecomposingRecordingPen, the same measure contract ② uses) against the shipped one. So the glyphs that actually grew taller are 1128 of 1723 and 1115 of 1694 — the vertical change is not universal, and the counts are the honest way to say so. Of the refitted glyphs, 593 (Regular) / 577 (Bold) already reached outside the 漢 box at equal scaling, and none of either face reaches outside it that did not already (the Regular face's count went 593 → 592, i.e. the stretch removed one rather than adding any).All three gates passed on both faces (
verify.py, run against the upstream file the converter actually read, at the declared ratio and stretch): contract ① — in-scope code points outside the declared band (ink ≤A × 1.15, centred when it overhangs, advance== A): 0, with the widest in-scope ink going 1024 → 622 on both faces (the declared room is 621.0, and 622 is where the integer grid lands); contract ② — body glyphs whose outlines changed: 0 of 13,101 on each face; contract ③ — refitted glyphs whose top and bottom edges differ from what the declaredvstretchand the measured wall imply: 0 of 1723 (Regular) and 0 of 1694 (Bold), checked as an equality in both directions, so bytes baked at a different stretch would fail whichever way they differed — and the half of that gate which recomputes no formula (③c) reports 0 glyphs newly outside the wall on both faces (largest excess 0.5 units against a tolerance of 1). In-scope is load-bearing rather than a hedge: the pipeline deliberately excludes ASCII / Latin-1, the BMP private use area, box drawing and block elements, and everything not width 1 (font-packages/README.md, "Terminal width conventions"). This package ships the measured instance of that: in both faces (A= 540)©(U+00A9) advances 540 with ink−1 … 541— width 1, one unit outside the cell on each side, and correctly untouched.The family and style names in the
nametable are rewritten (HackGen Console→ShirokumaGen Term, with nameID 3 carrying; derived); nameID 0, the copyright field, is carried through byte-identically (re-measured on the output) — the four bracketed notices are quoted inLICENSE.txt§1. The style bits move with the names:OS/2.fsSelectionbit 5 (BOLD) and bit 6 (REGULAR), andhead.macStylebit 0, are derived from the style rather than inherited. Measured on the output: RegularfsSelection=0x0040/macStyle=0x0000, BoldfsSelection=0x0020/macStyle=0x0001. Upstream'sfsSelectionalready carried the right bit on each face;head.macStylebit 0 did not (the Bold face had0x0000), so that single bit is the only metadata the conversion moved beyond the names.Reproducibility, as measured, not as promised: re-running the pipeline on the same machine, from the same input, with the tool versions above, produced byte-identical output — the sha256s below. Nothing is claimed about a different fontTools or Python: that was not tested, and neither was the effect of environment variables beyond the three fixed above.
3. Output (what is shipped)
Measured on the placed files, and recorded in scripts/font-fit/checksums.json
as the same values — npm test compares the two, so a TTF and its record can only
move together, and it compares the recorded fitRatio and vstretch against the
manifest as well.
| placed at | sha256 (placed) | fitRatio | vstretch |
|---|---|---|---|
fonts/ShirokumaGenTerm-Regular.ttf |
a1ae61cd72e9ec6eb2869e4581adcb1f56f99f08bb9e6b2d232f09b50bccdcf7 |
1.15 |
1.3 |
fonts/ShirokumaGenTerm-Bold.ttf |
c31cd509f2a909fc799e28a0c0948842c00a089f4ee332028a0a3f5463b936ea |
1.15 |
1.3 |
Reserved Font Name check (measured)
OFL 1.1 §3 forbids a Modified Version from carrying a Reserved Font Name, so a
converted package must establish which names are reserved before choosing
its own. The check is scripts/font-fit/check-license.py, not a hand-built
grep: it enumerates every file under the package, decides text from font from
binary by the leading bytes rather than by file name, and opens the fonts to
read their name table — the three pitfalls font-packages/README.md documents,
one of which no file search can reach at all.
~/.local/share/fonttools-venv/bin/python scripts/font-fit/check-license.py \
font-packages/font-shirokumagen --derived-name 'ShirokumaGen Term'
Exit status 0. Every line the tool printed, verbatim:
走査: テキスト 7 / フォント 2 / バイナリ除外 1 / 拡張子で除外 0 / 読めず 0 / name レコード decode 失敗 0
バイナリとして飛ばしたパス(中は見ていない・zip は展開しない):
- font-packages/font-shirokumagen/icon.png
記録ファイルとして外したパス(README.md / README.ja.md——この検査の記録そのもの。読ませるなら --all-files):
- font-packages/font-shirokumagen/README.ja.md
- font-packages/font-shirokumagen/README.md
一致行: 10
--- 一致行(全行・省略なし)---
font-packages/font-shirokumagen/LICENSE.txt:79: RESERVED FONT NAME note at the end of section 1.
予約名候補: (宣言なし——散文・定義文)
font-packages/font-shirokumagen/LICENSE.txt:109: Copyright (c) 2019, Yuko OTAWARA. with Reserved Font Name "白源", "HackGen"
予約名候補: '白源', 'HackGen'
font-packages/font-shirokumagen/LICENSE.txt:118: RESERVED FONT NAME: the HackGen line above reserves "白源" and "HackGen" (OFL
予約名候補: '白源', 'HackGen', 'the HackGen line above reserves', 'OFL'
font-packages/font-shirokumagen/LICENSE.txt:121: contains neither. Four reserved font names are declared anywhere in this
予約名候補: (宣言なし——散文・定義文)
font-packages/font-shirokumagen/LICENSE.txt:216: "Reserved Font Name" refers to any names specified as such after the
予約名候補: (宣言なし——散文・定義文)
font-packages/font-shirokumagen/licenses/LICENSE_GenJyuuGothic:33: "Reserved Font Name" refers to any names specified as such after the
予約名候補: (宣言なし——散文・定義文)
font-packages/font-shirokumagen/licenses/LICENSE_Hack:5: Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera"
予約名候補: 'Bitstream', 'Vera'
font-packages/font-shirokumagen/licenses/LICENSE_HackGen:10: Copyright (c) 2019, Yuko OTAWARA. with Reserved Font Name "白源", "HackGen"
予約名候補: '白源', 'HackGen'
font-packages/font-shirokumagen/licenses/LICENSE_HackGen:42: "Reserved Font Name" refers to any names specified as such after the
予約名候補: (宣言なし——散文・定義文)
font-packages/font-shirokumagen/licenses/LICENSE_NerdFonts:66: "Reserved Font Name" refers to any names specified as such after the
予約名候補: (宣言なし——散文・定義文)
--- 予約名: 引用符で宣言されたもの 4 件 ---
'Bitstream' ← font-packages/font-shirokumagen/licenses/LICENSE_Hack:5
'HackGen' ← font-packages/font-shirokumagen/LICENSE.txt:109, font-packages/font-shirokumagen/LICENSE.txt:118, font-packages/font-shirokumagen/licenses/LICENSE_HackGen:10
'Vera' ← font-packages/font-shirokumagen/licenses/LICENSE_Hack:5
'白源' ← font-packages/font-shirokumagen/LICENSE.txt:109, font-packages/font-shirokumagen/LICENSE.txt:118, font-packages/font-shirokumagen/licenses/LICENSE_HackGen:10
--- 引用符なしで拾った候補: 2 件(宣言のことも過剰捕捉のこともある・判定には同じく使う)---
'OFL' ← font-packages/font-shirokumagen/LICENSE.txt:118
'the HackGen line above reserves' ← font-packages/font-shirokumagen/LICENSE.txt:118
↑ 照合に使う予約名は上下あわせて 6 件。散文由来の候補は出所の行を見て人が捨てる(落とすより拾いすぎる側に倒してある)。
--- 派生名の判定(部分文字列・NFKC + casefold)---
'ShirokumaGen Term': どの予約名も含まない → OK (照合した予約名 6 件)
Run again for the Japanese name this package is called by, since §3 constrains the primary font name as presented to users in either language. Exit status 0, verdict line verbatim:
--- 派生名の判定(部分文字列・NFKC + casefold)---
'白熊源': どの予約名も含まない → OK (照合した予約名 6 件)
Reading the record. The quoted group is the declaration: "白源" and
"HackGen" (upstream's own, at licenses/LICENSE_HackGen:10, restated in this
package's LICENSE.txt), plus "Bitstream" and "Vera" carried in by the Hack
document's reproduction of the Bitstream Vera License. The unquoted group is
the tool's deliberate over-capture — 'OFL' and the phrase fragment
'the HackGen line above reserves', both picked off this package's own prose
line explaining the conclusion. They are compared against the derived name like
any other candidate and recorded rather than tuned away; neither is a claim that
this package reserves that name. No row comes from the name table: the two
shipped faces' nameID 0 carries four copyright notices and no
with Reserved Font Name clause (measured), so the font-binary stage — the one a
grep cannot reach — found nothing here. Its presence in the run is what makes
that a result rather than an untested assumption.
ShirokumaGen Term and 白熊源 each contain none of the six candidates.
The reserved names are also why the derived name is not a suffixed
HackGen Console Term: mechanically appending Term to the upstream family
would have published a name containing a Reserved Font Name.
Console was dropped for a different reason, and that one is a naming rule
rather than a licence constraint (#298). font-packages/README.md states it
once, under "How a shipped family name is derived": the shipped name is the
upstream family plus " Term", minus any upstream variant word whose meaning
Term already carries. The question is what the word says — Console is
upstream's mark for the terminal-oriented member of a pair, which is the property
Term asserts, so the two would say one thing twice. (Code in
M PLUS 1 Code says something else, namely which typeface it is — the
monospaced sibling of the proportional M PLUS 1 — so there the word stays.)
That section, not this one, is where the rule and its limits are stated; it also
records why "does upstream publish the name left after removing the word?" is
corroboration rather than the test. The rename changes the name table and
nothing else: it was applied by
re-running the same pipeline on the same input at the same fitRatio and
vstretch, and every glyph's outline and every hmtx entry came out identical
to the #289 bytes (measured, all 19,795 / 19,797 glyphs per face; the only other
byte that moved is head.checkSumAdjustment, which is the whole file's own
checksum).
family name (measured, not assumed)
Measured on the converted files — the ones that ship. Read from the name table
directly:
| field | ShirokumaGenTerm-Regular.ttf |
ShirokumaGenTerm-Bold.ttf |
|---|---|---|
| nameID 1 (family) | ShirokumaGen Term |
ShirokumaGen Term |
| nameID 2 (subfamily) | Regular |
Bold |
| nameID 3 (unique ID) | ShirokumaGen Term Regular; derived |
ShirokumaGen Term Bold; derived |
| nameID 4 (full name) | ShirokumaGen Term Regular |
ShirokumaGen Term Bold |
| nameID 6 (PostScript) | ShirokumaGenTerm-Regular |
ShirokumaGenTerm-Bold |
OS/2.usWeightClass |
400 |
700 |
fvar |
absent (static) | absent (static) |
Neither face carries a nameID 16 / 17 pair; nameID 1 is the family, and it is
identical across the two, which is what makes pairing them under a single CSS
font-family legitimate. The injection does not depend on it — @font-face
declares a family name rather than reading one, and CSS font matching never
consults a file's internal names; font-packages/README.md states that
mechanism in full. This table is evidence about the fonts, not about the
mechanism.
ShirokumaGen Term is the value this extension's providedFont.family
carries, and it must stay identical to the consumer-side registry entry
(tmux-opener's FONT_REGISTRY).
Monospace metrics (measured)
npm test in the repository root (test/font-metrics.test.js) covers each TTF
in this package — it globs them, so the bold face is asserted on every run too:
- unitsPerEm 1024; every ASCII printable codepoint 0x21–0x7E is present in cmap and shares one advance A = 540.
- The fullwidth samples 一 / 漢 / あ / ん / ア each advance 1080 = 2A.
- The two faces agree with each other: same unitsPerEm, same A.
What the refit changed and what it left alone is the point worth stating
precisely, because this package is the opposite case from font-mplus1code:
- The refit sets a selected glyph's advance to the cell width, and here it did so 1656 times in the Regular face and 1621 times in the Bold — every one of them 1080 → 540, measured against the upstream inputs. Upstream designs these ambiguous-width symbols full-width; a terminal counts them as one column.
- Nothing else moved. No other advance in either file differs from upstream, and body glyphs are untouched in outline too — contract ② in "Provenance", verified glyph by glyph, 0 of 13,101 per face.
- The cell grid does not move: A stays 540 before and after, full-width glyphs
stay 1080, and gid 0 (named
.notdef#0#0#0in these faces, which is whyfont-packages/README.mdstates the rule over gid 0 rather than over a glyph name) stays 540 — the last one matters because a code point missing fromcmapresolves to gid 0, and a reader that takes its width would report every missing symbol as a tidy half-width pass.
Coverage (measured, and what conversion cannot do)
scripts/font-fit/survey.py over the shipped faces, against the upstream ones:
- cmap size 18,201 code points (Regular) / 18,180 (Bold) — the largest of the families bundled in this repository.
- Of the 26 everyday symbols the survey samples, 26 are present in both faces
— no
—(absent) row at all. That is the half the conversion cannot supply: a refit moves glyphs, it never adds one, so a face missing a symbol stays missing it and the OS fallback draws it. - Before conversion, 14 of the 26 did not fit their cell, and the cause is
overwhelmingly (13 of the 14) the advance rather than a disorderly outline —
which is exactly the shape
fit.pysettles. Stated as measured: 13 of the 14 carry the full-width advance (2A = 1080), and those 13 fail the ink test as well, necessarily — ink drawn on a 1080-unit advance cannot stay inside a 540-unit box. The fourteenth is‰(U+2030), already on advance 540 with ink−1…541: a one-unit overhang on each side, so what it fails is the ink test alone. That is not the same as being left alone, and its bytes are not upstream's. The ±1 overhang is whatverify.py's contract ① tolerates;fit.py's "already fits" test is a different, stricter thing (x0 >= 0 and x1 <= A and …), sox0 = −1puts‰into the refit plan. Being in the plan costs it nothing horizontally — its ink is well insideroom(540 × 1.15 = 621), so the width factor iss = 1.0and the advance stays 540 — but the vertical factor still applies, and it stretched: measured against the upstream faces, the control box goes(−1, 0, 541, 695)→(−1, −72, 541, 767)in the Regular face and(−1, 0, 541, 695)→(−1, −80, 541, 775)in the Bold, with the vertical centre (347.5) preserved. After conversion 0 in-scope code points fail the advance condition in either face (measured:adv≠箱0 of 2916 in-scope code points in the Regular face, 0 of 2895 in the Bold). - The survey's
○column is stricter than the conversion's contract and does not move: it asks for ink inside[0, A], while the contract allows ink up toA × 1.15centred on the cell. So the same 14 samples stay△after conversion — they are now bounded and symmetric overhang rather than full-width glyphs squeezed into one cell. Do not read that column as "the conversion did nothing"; read the advance numbers above.
About this convention
To repeat: VS Code has no official mechanism for sharing fonts between
extensions. The borrowing steps are an unofficial, homegrown convention agreed
between the supplier (this extension) and the consumer (tmux-opener and the
like); the providedFont contract field is the source of truth for that
agreement, and font-packages/README.md is the source of truth for the
convention's description. If you change the shape of the contract field or the
location of the font file, update both sides together, keeping in mind that
consumers will break silently.