Skip to content

Reference for #60: real font weights -- the picker offers the family's own faces, bundled Nerd Font ships all sixteen - #64

Open
zcg wants to merge 6 commits into
ELY-Open-Source:mainfrom
zcg:font-weight-combobox
Open

zcg wants to merge 6 commits into
ELY-Open-Source:mainfrom
zcg:font-weight-combobox

Conversation

@zcg

@zcg zcg commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Reference for #60. Not seeking a merge — see the last section before reviewing the diff.

What this changes

Before: the weight picker offered Windows Terminal's eleven named weights (100–950) whatever the family held. The bundled Nerd Font shipped one face — JetBrainsMonoNerdFontMono-Regular — so:

  • every weight under 400 drew the same Regular (DirectWrite cannot synthesise thinner)
  • every weight over 400 drew Regular with a synthesised bold outline (bolder at the cost of filled counters at 13–16 px)
  • every italic was a slant of the upright

After:

  1. The bundle ships sixteen real faces — the eight weights and their italics of JetBrainsMonoNerdFontMono, in one xz archive: 1.87 MB against 39 MB unpacked. scripts/fonts.sh writes it from a Nerd Fonts release. Unpacked once on first run into %LOCALAPPDATA%/ELYTerminal/fonts (XDG data on Unix), read back afterwards — 249 ms once, none later. The binary shrank from 28 to 26.2 MB, since one face was 2.45 MB uncompressed.

  2. The weight list is the family's own. weights_for_family asks the platform what faces a family ships, and the picker offers only those — the bundled family shows [100, 200, 300, 400, 500, 600, 700, 800], no 900/950 it cannot draw, and no Custom slider whose steps were never real. Each entry is named with its number — 粗体 700 — and a family's own weight like 270 reads as the number it is.

  3. A monospace-only switch in Settings, on by default: the picker offered all 146 families the system names, of which 42 are monospaced.

  4. 0.2.2 settings keep loading. font_weight is now a whole number, as Windows Terminal's is; a deserializer reads both 500 and 500.0, so no settings file fails to start.

Verified on Windows 11: weight changes are visibly real at every step, italics draw real italic glyphs, the second run reads the cache without unpacking, and cargo test passes 419 (one pre-existing PowerShell failure also on main).

Why the vendor directories are here

The core of this PR is an API gap, and closing it upstream is your call, not mine — this branch vendors three crates only so it runs as it stands:

vendored size our change where it belongs
vendor/gpui-pre 3.0 MB 2 files, ~20 lines: PlatformTextSystem::weights_for_family + is_monospaced, both defaulted cannot be PRed — gpui-pre is a snapshot of zed, it has no repository of its own
vendor/gpui-pre-windows 0.5 MB 1 file, ~48 lines: DirectWrite's IDWriteFontFamily / IDWriteFont1 a PR to ELY-Open-Source/gpui-pre-windows
vendor/ely 10 MB 1 file, 12 lines: FontPicker::monospace(bool) a one-line PR to Ely

If you take the two PRs to your forks and drop the vendored paths for git dependencies, this branch collapses to ~600 lines of ELYTerminal code and the vendor directories disappear.

The defaulted trait methods mean the macOS and Linux forks compile unchanged; carrying the same two calls into gpui-pre-macos / gpui-pre-linux (CoreText and font-kit both answer them) is the natural follow-up for those platforms.

Not seeking a merge

We are outside contributors without push access to the org's forks, so the vendors are how this branch runs. Treat the branch as a working reference: take the pieces, re-point them at your forks, or close it — whatever serves the project. The commits are split along those seams (7caa487 the bundle, 9fae8cb the trait, 217debb the picker, f84f2eb the rest) so the pieces can be cherry-picked apart.

@zcg

zcg commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author
PixPin_2026-11-03_15-29-43 PixPin_2026-11-03_15-29-52

@zcg
zcg marked this pull request as draft November 3, 2026 08:03
@zcg

zcg commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

我重新思考了一下,可能需要大改,所以我把它改成了草稿。根据我和AI沟通和交流,我发现上面所谓的这些字重和和Windows Terminal上面的这些所谓的字重,其实都被定死了,而且它的渲染其实都是他们自己合成的。他们所谓的这个改变我们的这些字重,其实只是在字的轮廓外边慢慢的画粗或者慢慢的变细,但是不是真实的已经安装的那些字体,那些静态的那些字重的真实展示。所以我觉得存在很多问题,所以我让AI现在在尝试让AI去做动态的去获取系统里面那些字体的真实的那些字重,然后并真实的展现出来。这就要求那能够获取并在字重那个选择框,根据你选择的那个字体去动态的展示它已经安装了那些字重,然后再去选择和应用,这样的话是真实且有效的,我觉得这才是最完美的最合适的。所以我现在在本地已经尝试看到,已经看到效果了,而不是那些自己渲染出来的合成的那些假字重,我认为的是假字重。但是在本地我这里开发时候,AI的方案是需要涉及到去改一下GPUI,因为GPUI没有获取相对应的这些东西,而且会涉及到Linux、Windows和Mac。由于我本地只有Windows,所以我尝试了一下Windows的,我这边体验还行。至于Linux和Mac,可能需要大佬的帮助。

我知道有些人上面字太长太多,看不过来。那我就简单的说一句话,希望你能理解,就是目前的这些字体字重的机制是掩耳盗铃。想做到彻底的最好,那就得真实的获取,真实的展示。用的是字体文件里设计师画的真实的 Bold 轮廓 + 它自带的 hinting

…ts and their italics

Nerd Fonts' JetBrainsMono release carries 98 faces across six variants, and the
terminal drew one of them: JetBrainsMonoNerdFontMono-Regular. A family with one
face has one weight, so every weight the picker offered above 400 was DirectWrite
synthesising a bolder outline, every weight below 400 drew the same Regular, and
every italic was a slant of the upright.

assets/fonts now holds the sixteen faces of JetBrainsMonoNerdFontMono -- the eight
weights and their italics -- in one xz archive, 1.87 MB against the 39 MB they
unpack to. `scripts/fonts.sh` writes it from the release tarball after a bump.

The archive is unpacked on the first run and kept in
%LOCALAPPDATA%/ELYTerminal/fonts (XDG data on Unix), so later runs read it back
without touching the archive: 249 ms once, none after.
… real faces

Two platforms already expose the metadata: DirectWrite answers
IDWriteFontFamily::GetFontCount and IDWriteFont1::IsMonospacedFont, and
font-kit answers the same for macOS and Linux. gpui carried neither through.

vendor/gpui-pre is zed's gpui-pre 0.3.8 snapshot with one addition:
PlatformTextSystem::weights_for_family and ::is_monospaced, both defaulted, so a
platform that cannot tell answers none and the caller keeps its own list. A
defaulted method means the macOS and Linux forks compile unchanged.

vendor/gpui-pre-windows adds the DirectWrite half: a family's faces by
IDWriteFontFamily, its monospace flag by IDWriteFont1. The custom collection is
asked first, so a bundled face answers before a system one of the same name.

The upstream fix belongs in the forks, not here: a fork of zed's snapshot cannot
be sent upstream, since gpui-pre has no repository of its own.
Settings gains Monospaced only, on by default: a terminal's font is monospaced,
and the picker offered all 146 families the system names, of which 42 are.

vendor/ely is Ely's sources with one addition: FontPicker::monospace(bool),
which filters inside the keyed state the picker already caches, so the list is
walked once per switch rather than once per frame.

The upstream fix is a one-line PR to Ely; it is vendored here only so this branch
runs as it stands.
The row offered Windows Terminal's eleven names over 100 to 990 whatever the
family held, so a pick could land on a weight the font has no face for and draw a
synthesised outline. It now asks the text system which weights the family ships,
offers those, and names each with its number beside it -- "Bold 700" -- so a
family's own weight, 270 among them, reads as the number it is.

Custom and its slider are gone: a slider over a range the family does not hold
promised steps that were never drawn.

font_weight holds a whole number, as Windows Terminal's fontWeight does. A file
0.2.2 wrote names it as a float -- "font_weight": 500.0 -- since it held an f32; a
deserializer reads either form, so an upgrade keeps the weight and no settings
file fails to start. Only font_weight takes it.
@zcg
zcg force-pushed the font-weight-combobox branch from dc72e63 to f84f2eb Compare November 3, 2026 09:50
@zcg zcg changed the title Issue #60: the weight is Windows Terminal's, a list of names and Custom Reference for #60: real font weights -- the picker offers the family's own faces, bundled Nerd Font ships all sixteen Nov 3, 2026
@zcg

zcg commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

本PR不寻求直接合并,因为这涉及到跨仓库的改动,我采用了vendor的方式方便开发,而且我也没有权限可以跨仓库去改动和提交。违反了工程师的对仓库简洁优雅的原则。但希望对作者决策和采纳有所帮助。

@zcg

zcg commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

而且本人认为,这样子有利于提升字体清晰度、锐利度。但由于我没有实际去测量和对比,我也不知道是否真的有提升。但想来理论上,如果改变字重,那么字体应该会比原版要清晰锐利一点吧!也许!
下面是AI说的:

为什么真字面比合成字重更清晰、锐利

三条物理理由

1. 合成加粗的墨量是「错的」
DirectWrite 合成 = 轮廓等量外扩。真 Bold 是设计师重画的——竖画加粗多、横画加粗少。在 13–16px 下:

  • 合成:e a 8 的内孔被外扩蚕食 → 糊
  • 真字面:内孔按目标字重重新设计 → 保持清晰

2. hinting 只有真字面才有
每个真字重文件都自带 hinting(笔画对齐像素网格的指令)。合成外扩落在半像素上,边缘发灰发虚——这是「锐利度」差异的最大来源。
3. 细体从「不存在」变成「存在」
旧版选 100–300 画的就是 Regular 的墨量,没有任何变化可言。现在 Thin 是真的细,笔画 1px 整齐落格。

收益分布(诚实版)

场景 提升
常规字重 400 零 —— 本来就用真 Regular
粗体(程序输出 [1m) 明显 —— 真 Bold vs 合成外扩
细体 100–300 从无到有
字重调到 500/600 明显 —— Medium/SemiBold 是全新字面,旧版全画成 Regular

「所有文字都变清晰」是错的——常规 400 完全没变,因为旧版本来就用真 Regular。
收益集中在:粗体不再糊、细体终于存在、中间字重是真字面。

@zcg

zcg commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

至于这个PR如何取舍,如何用,是雷电芽衣的事。用完或者决定不用,都可以自行关闭。

@zcg
zcg marked this pull request as ready for review November 3, 2026 10:02
@zcg

zcg commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author
PixPin_2026-11-03_18-08-13 PixPin_2026-11-03_18-09-16 我本地的现状演示图片。

… once

Three fixes from a pass over the branch:

A family the platform cannot tell about was offered 400 and 700, so a pick of
700 on a family that holds one face drew a synthesised bold -- the very thing
this branch removes. The fallback is 400 alone now, the weight nearly every
family holds; no pick promises a face the family cannot draw.

The weights were read on every redraw of the settings page, and the cursor
preview blinks, so DirectWrite walked the family's faces several times a second
while the page was open. The page holds the family's weights now and asks again
only when the family moves.

The zh names are set straight: Light is a weight, not a colour, and Black is a
weight, not a colour -- 浅色 and 黑色 are colour words. The row reads 细体 300
and 黑体 900, and the rest of the scale follows: 极细体 特细体 次细体 常规 中等
半粗体 粗体 特粗体 黑体 超黑体.
…p their names

Two fixes from a second pass over the branch:

A cache written by an older bundle was trusted when its files happened to be the
same size as the new ones, so an upgrade could keep drawing the old faces until
the folder was cleared by hand. The folder now holds a stamp of the bundle's
size and hash, and a cache that does not match it is unpacked and rewritten.

The temp file a face was written through lost its .ttf when
`with_extension("tmp")` replaced it -- Bold.ttf became Bold.tmp -- which would
have collided had two faces differed by extension alone. The temp now appends
`.tmp` to the whole name and keeps it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants