Mgen+ 1mn Term (font resource for extensions)Extension ID: A general-purpose font resource extension that supplies the Mgen+ 1mn Term font
to other extensions. It has no UI and no commands of its own ( Mgen+ 1mn Term is a modified version of Mgen+ 1mn, 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. 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 38 units of a 512-unit cell (about 7.5%). A symbol may
therefore touch its neighbour slightly; what it never does is reach the
squashing threshold. Advance widths are not part of that: the cell grid does not
move, and this file's The two axes are scaled by different factors (since #293): the width goes to
Bundled font
Two faces of one family, each a separate static TTF converted from the corresponding face of the same upstream distribution.
Every See Copyright and attribution (three parties)Mgen+ fills the glyphs M+ OUTLINE FONTS does not cover with glyphs from Source Han Sans, so three parties are credited. Which source states what is kept separate below, because two of the three are named as copyright holders in so many words and the third is not. Copyright holders, quoted verbatimThe bundled TTFs' own
The distribution's own README (
Note the Reserved Font Name The third party: 自家製フォント工房 (Jikasei Font Studio)Neither the name table nor the README's copyright section carries a verbatim
copyright line for 自家製フォント工房. It is credited here as the author and
distributor of the composed font — the work of selecting, merging and fitting
the two upstream glyph sets into Mgen+ — which the distribution states in a
separate section, 「■ 頒布元」 (
Read that as an attribution of authorship and distribution, not as a quoted copyright notice. Licenses, and a note on the linksThe font is licensed under the SIL Open Font License, Version 1.1 (full text
in Project links, for convenience: Mgen+ http://jikasei.me/font/mgenplus/,
Source Han Sans https://github.com/adobe-fonts/source-han-sans,
M+ FONTS PROJECT http://mplus-fonts.osdn.jp/. These are current addresses,
not quotations from the distribution — its 「■ 改変元」 section cites
Scope limitation: it does not work in
|
| archive entry | sha256 (entry, on extraction) |
|---|---|
mgenplus-1mn-regular.ttf |
52098e94e7e3673ea1457179d9841754ce07044868d146ecacf606464e8486f3 |
mgenplus-1mn-bold.ttf |
1d720fb23c0362240836547ef8ebe889db8234cee4c432568f568af5c8c4af97 |
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,dump-widths.mjsproduces the cell-width table from@xterm/addon-unicode11's own provider — a hand-written width table is forbidden.Which converter, identified by content rather than by commit — introduced by issue #275, and 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.json0fed27ae2614fd0cbe570cde156bb4ed5ff32fe7a0a7c814b97d71acaf49629bWhy not a commit SHA. The first draft of this section recorded one, and it was false within the same PR: the converter was then changed (a bold face was flagging itself Regular), the output moved, and the recorded SHA still pointed at the older converter — checking it out and re-running produced a different hash than the one recorded three lines below. That is not a slip that better care avoids: a git SHA is content-addressed, so a file cannot name the commit that contains it. Recording one means either splitting the TTFs, the
checksums.jsonrecord and this table across two commits — the one coupling R13 exists to prevent — or leaving a placeholder for someone to substitute, which ships silently when they forget. Hashing the scripts has neither problem: the values are knowable while writing, they survive rebases and rewrites, and they identify the converter directly instead of via a commit that also carries unrelated changes.run.shadditionally appends-dirtyto the commit it prints when the working tree differs fromHEAD, so a provenance line generated from uncommitted edits can no longer be mistaken for one generated from a clean checkout.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).The
fitRatiothe run was driven by: 1.15 (manifest.json's top-level default; this package declares no override). Ink wider thanA × 1.15— 588.8 units of the 512-unit cell — is scaled down to it; wider ink is what "scaled" counts below. 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 = −78…843in the Regular face and−95…847in the Bold, asrun.shprinted them. The clamp bound the stretch on 357 of the 1001 refitted glyphs in the Regular face and 284 of 1020 in the Bold; of those, 144 and 121 respectively had no room left at equal scaling and were left completely unmoved (the clamp's floor is 1.0). So the glyphs that actually grew taller are 857 of 1001 and 899 of 1020 — the vertical change is not universal, and the counts are the honest way to say so. Of the refitted glyphs, 141 (Regular) / 121 (Bold) already reached outside the 漢 box at equal scaling, and none of either face reaches outside it that did not already (measured on the shipped bytes against avstretch = 1.0bake of the same upstream). The horizontal side is untouched by this:hmtxis byte-identical to the pre-#293 release across all 18,109 (Regular) / 18,106 (Bold) glyphs (measured), and the widest in-scope ink is unchanged at 588.What the run changed, per face — measured, and reported by
run.sh:face glyphs refitted of which moved only / scaled already fitting Regular 1001 250 / 751 1344 Bold 1020 232 / 788 1327 "Moved only" grew and "scaled" shrank against the 0.96-ratio release: at 1.15 the room is wider than the cell, so a glyph whose ink already fits within 588.8 is re-centred at its original size rather than shrunk.
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 landing on the declared room exactly (Regular 1024 → 588, Bold 1032 → 588); contract ② — body glyphs whose outlines changed: 0 of 13,101; contract ③ — refitted glyphs whose top and bottom edges differ from what the declaredvstretchand the measured wall (this face's own 漢 y reach) imply: 0 of 1001 (Regular) and 0 of 1020 (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. Together those say the refit reached every glyph it was asked to reach, went exactly as far as the declaration and no further, and touched nothing else. 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"), so a Latin-1 glyph overhanging its advance is outside this count by design.The family and style names in the
nametable are rewritten (Mgen+ 1mn→Mgen+ 1mn Term); nameID 0, the copyright field, is carried through byte-identically (re-measured on the output, quoted above).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 — from #276 npm test compares the two, so a TTF and its
record can only move together. Since #291 the record carries the fitRatio
beside each sha256 and since #293 the vstretch as well, and npm test compares
both against the manifest too.
| placed at | sha256 (placed) | fitRatio | vstretch |
|---|---|---|---|
fonts/MgenPlus1mnTerm-Regular.ttf |
05973b8cb602325d735b27293df5cf103d90fb23192a737f2811e78bf9028adf |
1.15 |
1.3 |
fonts/MgenPlus1mnTerm-Bold.ttf |
c8f0fd9be1d08905ce28859b647912ba8fa5dd0ff2834ca61b2520d6338d953d |
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. Measured over every license text this package ships:
Superseded (#288). This
grepis no longer the procedure. Runscripts/font-fit/check-license.pyinstead — the steps are infont-packages/README.md("Reserved Font Name check"). The command below cannot reach thenametable, where a package can declare a reserved name that appears in no file at all (measured on Source Han Code JP, #285); it is also blind to license documents that are not UTF-8. The table below is kept as the record this grep produced, not as instructions: it predates thename-table stage, so anyfonts/*.ttf#0:nameID Nrow the tool reports is missing from it. Adding those rows is #289's work.
grep -rniI 'reserved font name' \
font-packages/font-mgenplus-1mn/LICENSE.txt \
font-packages/font-mgenplus-1mn/fonts/
Two flags and the path list are all load-bearing, and each of them was got wrong once in #275:
-i— OFL prose writes the phrase title-case (Reserved Font Name) but a section heading may shout it, and a case-sensitive grep silently drops those lines. That is how the first version of this table came to omitLICENSE.txt:62, the very line stating this package's conclusion.- The path list, not
--include='*.txt'— the M+ license documents this font derives from ship asLICENSE_E/LICENSE_J, with no extension, so a*.txtfilter reads right past them while the prose above claims to have searched every license text. NamingLICENSE.txtplus the wholefonts/directory covers them. (They contain no reserved font name — measured, 0 hits each — so the verdict was never in doubt; the search that produced it was.) -I—fonts/also holds the TTFs, and without it grep reportsBinary file … matchesand clutters a table that is supposed to be reproducible line for line.
A verdict is only as good as the search that produced it, so the command is written down and the table below lists every line it returns, not just the interesting ones:
| file:line | declares |
|---|---|
LICENSE.txt:31 |
no name — a forward reference to the note at :62 |
LICENSE.txt:51 |
'Source' (Adobe, in the quoted Source Han Sans copyright line) |
LICENSE.txt:62 |
'Source' — this package's own RESERVED FONT NAME note, restating the line above |
LICENSE.txt:65 |
no name — the same note's prose |
LICENSE.txt:67 |
no name — the same note naming the files it searched |
LICENSE.txt:146 |
no name — the OFL definition clause itself |
fonts/SIL_Open_Font_License_1.1.txt:31 |
no name — the same definition clause |
fonts/mplus-TESTFLIGHT-059/LICENSE_E |
no hits — searched, nothing to declare |
fonts/mplus-TESTFLIGHT-059/LICENSE_J |
no hits — searched, nothing to declare |
fonts/README_MgenPlus.txt |
no hits — searched, nothing to declare |
The name-table rows the grep could not reach (#289, measured with
scripts/font-fit/check-license.py). These are not corrections to the table
above — the name they declare is the one it already records — they are evidence
the text-only procedure was structurally blind to, added here because the
superseded note above says they were missing:
| source | declares |
|---|---|
fonts/MgenPlus1mnTerm-Regular.ttf#0:nameID 0 |
'Source' (plus the over-captures 'M+ OUTLINE FONTS' / 'OUTLINE' off the same line) |
fonts/MgenPlus1mnTerm-Bold.ttf#0:nameID 0 |
'Source' (same line, same over-captures) |
The tool's own verdict on the shipped bytes, verbatim:
'Mgen+ 1mn Term': どの予約名も含まない → OK (照合した予約名 9 件), exit 0.
Its text hit lines carry different line numbers from the table above because
LICENSE.txt has been rewritten since that grep was run; the table stays as
the record of what that command returned, which is what it is for.
'Source' is the only Reserved Font Name declared here, and neither
Mgen+ nor 1mn nor Term contains it, so Mgen+ 1mn Term is a permitted
name for this modified version. (For contrast, font-plemoljp-console declares
"PlemolJP" as reserved — which is one reason that package is not converted at
all.)
Accompanying documents (byte-for-byte, two-column rule)
Both columns are measured, in that order: the left one comes off the extracted
file in a scratchpad directory before anything was copied into the
repository, the right one off the committed file afterwards. The rule — and why
a single self-referential column would verify nothing — is stated once for every
package in font-packages/README.md.
| archive entry | sha256 (entry, on extraction) | placed at | sha256 (placed) |
|---|---|---|---|
SIL_Open_Font_License_1.1.txt |
697383c6addd5dd14d7e1ff053aaa44520ce86a58589eef1f9985ab66e08db08 |
fonts/SIL_Open_Font_License_1.1.txt |
697383c6addd5dd14d7e1ff053aaa44520ce86a58589eef1f9985ab66e08db08 |
README_MgenPlus.txt |
7ad4c1969beefb2af09c789a68b3da7bb4b628b2fae8b64fe1ce25429d5acf4d |
fonts/README_MgenPlus.txt |
7ad4c1969beefb2af09c789a68b3da7bb4b628b2fae8b64fe1ce25429d5acf4d |
mplus-TESTFLIGHT-059/LICENSE_E |
2d499d752eca1988766eeca70d4145fd94793579924702629181d0ed63dd51a7 |
fonts/mplus-TESTFLIGHT-059/LICENSE_E |
2d499d752eca1988766eeca70d4145fd94793579924702629181d0ed63dd51a7 |
mplus-TESTFLIGHT-059/LICENSE_J |
66b4cd071d8b2e8a69db344604c00aec7e81192dd9c0c9657979b142310cc3ab |
fonts/mplus-TESTFLIGHT-059/LICENSE_J |
66b4cd071d8b2e8a69db344604c00aec7e81192dd9c0c9657979b142310cc3ab |
They ship at their original archive-internal paths: the OFL 1.1 text that covers
Mgen+ itself, the distribution's own README_MgenPlus.txt (which states the
version and what the font is composed of), and the upstream M+ OUTLINE FONTS
licenses that Mgen+ derives from, kept under their mplus-TESTFLIGHT-059/
directory rather than flattened, so their subject stays unambiguous. They
describe the upstream font; this README is where the modification is
described.
family name (measured, not assumed)
Re-measured in #275 on the converted files, since those are what ships: the
conversion rewrites the name table, so a family string carried over from the
pre-conversion measurement would have been a claim about bytes this package no
longer contains. Read from the name table directly (nameID 1 / 2 / 4 / 6, and
nameID 16 / 17 where present) — every face this package ships is measured,
because the whole borrowing scheme rests on the two of them naming the same
typographic family:
| field | MgenPlus1mnTerm-Regular.ttf |
MgenPlus1mnTerm-Bold.ttf |
|---|---|---|
| nameID 1 (family) | Mgen+ 1mn Term |
Mgen+ 1mn Term |
| nameID 16 (typographic family) | Mgen+ 1mn Term |
Mgen+ 1mn Term |
| nameID 2 / 17 (subfamily) | Regular |
Bold |
| nameID 4 (full name) | Mgen+ 1mn Term Regular |
Mgen+ 1mn Term Bold |
| nameID 6 (PostScript) | MgenPlus1mnTerm-Regular |
MgenPlus1mnTerm-Bold |
OS/2.usWeightClass |
400 |
700 |
fvar |
absent (static) | absent (static) |
nameID 16 is the typographic family and it is identical across the two faces —
upstream designed them as one family and the conversion preserves that, which is
what makes pairing them under a single CSS font-family legitimate (and their
metrics agreement, below, an expected property rather than a coincidence). 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.
Mgen+ 1mn 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). It is recorded here as a measurement, not as a name chosen and
then assumed to have been applied. A face added or replaced here gets its own
column, measured — the table is the record of that measurement, not a
substitute for making it (#249).
Note for consumers: the pre-conversion name Mgen+ 1mn is not what these
files declare any more. tmux-opener keeps it working by registering it as an
alias and injecting an alias-named @font-face over the same files, so an
existing settings.json renders with the converted bytes; that is a
consumer-side compatibility layer, not a property of this package.
A note on fontconfig, which the pre-#275 version of this table was taken from:
fc-scan reports spacing as dual (90), not mono (100), for these faces —
normal for a CJK font, and precisely why fontconfig's spacing is not the
monospace evidence. The evidence is test/font-metrics.test.js, below.
Monospace metrics (measured)
npm test in the repository root (test/font-metrics.test.js) covers both
files and asserts this on every run:
- unitsPerEm 1024; every ASCII printable codepoint 0x21–0x7E is present in cmap and shares one advance A = 512.
- The fullwidth samples 一 / 漢 / あ / ん / ア each advance 1024 = 2A.
- The two faces agree with each other: same unitsPerEm, same A (#249). Each face being monospaced on its own is not enough — the terminal measures its cell from one family and paints both faces into it, so bold at a different scale would drift out of column while every per-file check still passed.
The values above are hmtx advances of the default instance, which for
these static faces is the whole font. These particular values are unchanged by
the conversion — but plenty of other advances are not, and the distinction is
worth stating exactly, because a looser version of this sentence shipped in #275
claiming the conversion never touches an advance at all, which is false:
- A width-1 code point's advance is set to the cell width, deliberately, and
for nearly all of them that is a change. That is half the
point of the refit: upstream draws nearly all of these at the full-width
advance 1024, and the conversion sets them to the cell width. Measured on the shipped files
against the upstream ones, 997 glyphs in the regular face and 1016 in the
bold went 1024 → 512, and 1024 → 512 is the only transition either file
contains. Both numbers were re-measured on the #291 bytes and are unchanged
—
fitRatiomoves ink, never advances, so thehmtxadvances are byte-identical to the 0.96-ratio release. Thehmtxtable as a whole is not: each entry is(advanceWidth, lsb), and moving a refitted glyph's ink moves itslsb, so against that release 875 entries in the regular face and 935 in the bold differ while 0 advances do (measured with fontTools, #292). - A body glyph's advance is not changed, and neither is its outline — that is contract ② in "Provenance", verified glyph by glyph.
- The cell grid does not move: A stays 512 and full-width glyphs stay 1024, which is why the numbers in this section still hold and why converted text sets in the same columns.
Conflating the second and third points with "no advance changed" is the mistake
that produced the false sentence. Contract ② is about the glyphs the refit did
not select; a selected glyph changes in outline, and in advance too whenever
upstream drew it full-width — which is nearly always, but not always. Measured:
1001 glyphs were refitted in the regular face and 997 of those changed advance
(1020 and 1016 in the bold); the 4 per face left over — ↑ U+2191, ↓ U+2193,
↕ U+2195, plus ȿ U+023F in the regular and Ϡ U+03E0 in the bold — are
glyphs upstream already drew one column wide, so the refit re-centred their ink
and left the advance where it was.