mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-26 16:02:34 +00:00
Merge pull request #939 from l0ng-ai/feat/720-font-smoothing
feat(fonts): make macOS stroke thickening configurable
This commit is contained in:
+4
-1
@@ -222,7 +222,10 @@ objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSResponder", "
|
||||
# `set_dock_icon_for_bare_binary` in main.rs.
|
||||
# NSUserNotification is the click-to-reveal notification path — see
|
||||
# `terminal::remote::macos_notify` for why it is driven directly.
|
||||
objc2-foundation = { version = "0.3", features = ["NSData", "NSString", "NSUserNotification"] }
|
||||
# NSUserDefaults (with NSDictionary / NSValue for the domain it takes) pins
|
||||
# `AppleFontSmoothing` for this process when `font_thicken` is off — see
|
||||
# `apply_font_thicken` in main.rs.
|
||||
objc2-foundation = { version = "0.3", features = ["NSData", "NSDictionary", "NSString", "NSUserDefaults", "NSUserNotification", "NSValue"] }
|
||||
|
||||
# Dictionary-based Chinese word segmentation for double-click selection
|
||||
# (`terminal::smart_select`), as a *fallback* where the OS has no tokenizer of
|
||||
|
||||
@@ -135,6 +135,16 @@ pub struct Config {
|
||||
pub font_family_bold: Option<String>,
|
||||
pub font_family_italic: Option<String>,
|
||||
pub font_features: Option<FontFeatures>,
|
||||
/// macOS only: let CoreGraphics font smoothing thicken glyph strokes — by
|
||||
/// more the lighter the text, which is why light-on-dark looks bolder.
|
||||
/// On by default, because it is what every macOS app draws with.
|
||||
///
|
||||
/// Off pins `AppleFontSmoothing` to `0` for this process alone, so glyphs
|
||||
/// are drawn at the face's own weight. gpui reads that preference once, the
|
||||
/// first time it rasterizes text, so a change applies at the next launch.
|
||||
/// Ignored elsewhere, where there is no such dilation to turn off.
|
||||
#[serde(default = "default_true")]
|
||||
pub font_thicken: bool,
|
||||
pub font_size: f32,
|
||||
pub line_height: f32,
|
||||
/// The interface's root font size, in pixels — everything outside the
|
||||
@@ -629,6 +639,7 @@ impl Default for Config {
|
||||
font_family_bold: None,
|
||||
font_family_italic: None,
|
||||
font_features: None,
|
||||
font_thicken: true,
|
||||
font_size: 15.0,
|
||||
line_height: 1.4,
|
||||
ui_font_size: default_ui_font_size(),
|
||||
@@ -1497,6 +1508,18 @@ mod tests {
|
||||
assert!(default_cfg.font_features.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_thicken_defaults_on_and_reads_an_explicit_off() {
|
||||
// On is what tty7 drew before the key existed, so a config that never
|
||||
// names it must keep rendering exactly as it did.
|
||||
let cfg: Config = serde_json::from_str("{}").unwrap();
|
||||
assert!(cfg.font_thicken);
|
||||
assert!(Config::default().font_thicken);
|
||||
|
||||
let cfg: Config = serde_json::from_str(r#"{"font_thicken":false}"#).unwrap();
|
||||
assert!(!cfg.font_thicken);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_features_round_trip_to_integer_valued_json() {
|
||||
let features: FontFeatures =
|
||||
|
||||
@@ -15,6 +15,7 @@ description: "The bundled default, fallback chains, ligatures, and why CJK needs
|
||||
| **Line height** | 1.4 | A multiple of the font size |
|
||||
| **Bold font** / **Italic font** | — | Distinct faces, when you want them |
|
||||
| **Font ligatures** | off | `calt`, `liga` and `clig` all stay off unless you ask |
|
||||
| **Thicken strokes** | on | macOS only — see [Stroke weight on macOS](#stroke-weight-on-macos) |
|
||||
|
||||
## Hack is bundled
|
||||
|
||||
@@ -71,6 +72,35 @@ on. Name them explicitly if you want both:
|
||||
}
|
||||
```
|
||||
|
||||
## Stroke weight on macOS
|
||||
|
||||
macOS font smoothing thickens glyph strokes, and by how much depends on the
|
||||
text colour: the lighter the text, the bolder it draws. That is why light text
|
||||
on a dark theme can look heavier in tty7 than in a terminal that turns the
|
||||
effect off, and why a thinner weight of the same family does not fully make up
|
||||
for it.
|
||||
|
||||
**Settings → Appearance → Terminal text → Thicken strokes** (`font_thicken`)
|
||||
turns it off, so glyphs draw at the face's own weight whatever their colour:
|
||||
|
||||
```json
|
||||
{
|
||||
"font_thicken": false
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
The change applies the next time tty7 starts: the preference is read once,
|
||||
when text is first drawn. It covers tty7 alone — no other app, and not the
|
||||
system-wide setting.
|
||||
</Note>
|
||||
|
||||
Leaving it on changes nothing, so an existing
|
||||
`defaults write com.github.tty7 AppleFontSmoothing -int 0` keeps working. This
|
||||
is the same switch as Ghostty's `font-thicken` and iTerm2's thin strokes, with
|
||||
tty7 defaulting to the macOS look. Windows and Linux do not thicken strokes, so
|
||||
the key does nothing there.
|
||||
|
||||
## CJK and the two-column grid
|
||||
|
||||
<Info>
|
||||
|
||||
@@ -35,6 +35,7 @@ failing the file.
|
||||
| `font_family_bold` | string | — | A distinct bold face. |
|
||||
| `font_family_italic` | string | — | A distinct italic face. |
|
||||
| `font_features` | object | — | OpenType tags, e.g. `{"calt": true, "liga": 1}`. Four alphanumeric characters per tag. |
|
||||
| `font_thicken` | bool | `true` | macOS: let font smoothing thicken strokes, light text most. `false` draws glyphs at their own weight. Applies after a restart. |
|
||||
| `font_size` | number | `15` | Terminal text size in px (4–256). |
|
||||
| `line_height` | number | `1.4` | Multiple of the font size (0.5–4). |
|
||||
| `ui_font_size` | number | `16` | The interface's root size in px (12–24). |
|
||||
|
||||
+37
@@ -441,6 +441,41 @@ fn set_dock_icon_for_bare_binary() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns CoreGraphics' stroke thickening off for this process when
|
||||
/// `font_thicken` is off; with it on there is nothing to do, and whatever the
|
||||
/// system (or a hand-written `defaults write`) says stands.
|
||||
///
|
||||
/// gpui decides whether to dilate a glyph from `AppleFontSmoothing`, read with
|
||||
/// `CFPreferencesCopyAppValue` the first time it rasterizes text and cached for
|
||||
/// the life of the process. So this has to land before the application exists,
|
||||
/// and a change waits for the next launch.
|
||||
///
|
||||
/// The value goes into the argument domain — the volatile one that
|
||||
/// `-AppleFontSmoothing 0` on the command line would fill. It outranks both this
|
||||
/// app's persisted defaults and the global domain, and it lives only in this
|
||||
/// process's memory: nothing reaches disk, so no other app sees it and a later
|
||||
/// launch with the key back on does not inherit it.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn apply_font_thicken(thicken: bool) {
|
||||
use objc2_foundation::{
|
||||
NSArgumentDomain, NSMutableCopying, NSNumber, NSUserDefaults, ns_string,
|
||||
};
|
||||
|
||||
if thicken {
|
||||
return;
|
||||
}
|
||||
let defaults = NSUserDefaults::standardUserDefaults();
|
||||
// SAFETY: a Foundation constant, initialized before `main` runs.
|
||||
let domain = unsafe { NSArgumentDomain };
|
||||
// Merged into, not replaced: the domain already holds any `-Key value`
|
||||
// pairs the process was launched with.
|
||||
let arguments = defaults.volatileDomainForName(domain).mutableCopy();
|
||||
arguments.insert(ns_string!("AppleFontSmoothing"), &*NSNumber::new_i32(0));
|
||||
// SAFETY: keys are `NSString` and values property-list objects, which is
|
||||
// the shape a defaults domain requires.
|
||||
unsafe { defaults.setVolatileDomain_forName(&arguments, domain) };
|
||||
}
|
||||
|
||||
/// Delivers Finder document opens and LaunchServices URL opens after gpui has
|
||||
/// created the application. The native callback queues requests; UI state is
|
||||
/// then changed on gpui's application loop.
|
||||
@@ -593,6 +628,8 @@ fn main() {
|
||||
|
||||
let (config, config_outcome) = crate::core::config::Config::load_with_outcome();
|
||||
let gui_language = config.gui_language.clone();
|
||||
#[cfg(target_os = "macos")]
|
||||
apply_font_thicken(config.font_thicken);
|
||||
|
||||
// After the PATH enrichment above, which is what makes the candidate scan
|
||||
// see the user's real PATH rather than the stub a Finder launch inherits —
|
||||
|
||||
@@ -3497,6 +3497,12 @@ impl Tty7App {
|
||||
self.update_config(cx, |cfg| cfg.show_tray_icon = on);
|
||||
}
|
||||
|
||||
/// Saved only: gpui reads the preference this drives once per process, so
|
||||
/// it takes hold at the next launch (see `apply_font_thicken` in main.rs).
|
||||
pub(crate) fn set_font_thicken(&mut self, on: bool, cx: &mut Context<Self>) {
|
||||
self.update_config(cx, |cfg| cfg.font_thicken = on);
|
||||
}
|
||||
|
||||
pub(crate) fn set_macos_option_as_alt(&mut self, on: bool, cx: &mut Context<Self>) {
|
||||
self.update_config(cx, |cfg| cfg.macos_option_as_alt = on);
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsFontLigaturesDesc => {
|
||||
"Enable common programming ligature features for terminal text."
|
||||
}
|
||||
L10nKey::SettingsFontThicken => "Thicken strokes",
|
||||
L10nKey::SettingsFontThickenDesc => {
|
||||
"macOS font smoothing: draws text a little bolder, light text most. Takes effect after restarting tty7."
|
||||
}
|
||||
L10nKey::SettingsCursor => "Cursor",
|
||||
L10nKey::SettingsCursorShape => "Cursor shape",
|
||||
L10nKey::SettingsCursorShapeDesc => "How the terminal cursor is drawn.",
|
||||
@@ -842,6 +846,9 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsSearchFocusFollowsMouseKeywords => "pane hover activate",
|
||||
L10nKey::SettingsSearchFontFamilyKeywords => "typeface monospace typography",
|
||||
L10nKey::SettingsSearchFontLigaturesKeywords => "typography glyph fira",
|
||||
L10nKey::SettingsSearchFontThickenKeywords => {
|
||||
"font smoothing thicken bold weight thin dilation antialiasing AppleFontSmoothing"
|
||||
}
|
||||
L10nKey::SettingsSearchFontSizeKeywords => "typography text bigger smaller zoom",
|
||||
L10nKey::SettingsSearchForwardSshLoopbackLinksKeywords => {
|
||||
"ssh remote port tunnel localhost forward links ports autoforward detect"
|
||||
|
||||
@@ -135,6 +135,10 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsFontLigaturesDesc => {
|
||||
"ターミナルテキストで一般的なプログラミング用リガチャー(合字)を有効にする"
|
||||
}
|
||||
L10nKey::SettingsFontThicken => "ストロークを太くする",
|
||||
L10nKey::SettingsFontThickenDesc => {
|
||||
"macOS のフォントスムージング:文字をやや太く描画し、明るい文字ほど太くなる。tty7 の再起動後に反映"
|
||||
}
|
||||
L10nKey::SettingsCursor => "カーソル",
|
||||
L10nKey::SettingsCursorShape => "カーソルの形状",
|
||||
L10nKey::SettingsCursorShapeDesc => "ターミナルカーソルの描画方法",
|
||||
@@ -876,6 +880,9 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsSearchFontLigaturesKeywords => {
|
||||
"タイポグラフィ グリフ fira font ligatures typography glyph fira"
|
||||
}
|
||||
L10nKey::SettingsSearchFontThickenKeywords => {
|
||||
"フォントスムージング 太字 細字 ウェイト font smoothing thicken bold weight thin AppleFontSmoothing"
|
||||
}
|
||||
L10nKey::SettingsSearchFontSizeKeywords => {
|
||||
"タイポグラフィ 文字 拡大 縮小 ズーム font size typography text bigger smaller zoom"
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@ l10n_keys! {
|
||||
SettingsItalicFontDesc,
|
||||
SettingsFontLigatures,
|
||||
SettingsFontLigaturesDesc,
|
||||
SettingsFontThicken,
|
||||
SettingsFontThickenDesc,
|
||||
SettingsCursor,
|
||||
SettingsCursorShape,
|
||||
SettingsCursorShapeDesc,
|
||||
@@ -644,6 +646,7 @@ l10n_keys! {
|
||||
SettingsSearchFocusFollowsMouseKeywords,
|
||||
SettingsSearchFontFamilyKeywords,
|
||||
SettingsSearchFontLigaturesKeywords,
|
||||
SettingsSearchFontThickenKeywords,
|
||||
SettingsSearchFontSizeKeywords,
|
||||
SettingsSearchForwardSshLoopbackLinksKeywords,
|
||||
SettingsSearchGeminiKeywords,
|
||||
|
||||
@@ -119,6 +119,10 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsItalicFontDesc => "斜体文字使用的字体;默认由主字体合成。",
|
||||
L10nKey::SettingsFontLigatures => "字体连字",
|
||||
L10nKey::SettingsFontLigaturesDesc => "为终端文字启用常见的编程连字特性。",
|
||||
L10nKey::SettingsFontThicken => "笔画加粗",
|
||||
L10nKey::SettingsFontThickenDesc => {
|
||||
"macOS 字体平滑:文字画得稍粗,浅色文字最明显。重启 tty7 后生效。"
|
||||
}
|
||||
L10nKey::SettingsCursor => "光标",
|
||||
L10nKey::SettingsCursorShape => "光标形状",
|
||||
L10nKey::SettingsCursorShapeDesc => "终端光标的绘制方式。",
|
||||
@@ -771,6 +775,9 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsSearchFontLigaturesKeywords => {
|
||||
"字体连字 连字 字形 typography ligatures glyph fira"
|
||||
}
|
||||
L10nKey::SettingsSearchFontThickenKeywords => {
|
||||
"字体平滑 加粗 变细 字重 笔画 font smoothing thicken bold weight thin AppleFontSmoothing"
|
||||
}
|
||||
L10nKey::SettingsSearchFontSizeKeywords => {
|
||||
"字号 字体大小 文字 放大 缩小 typography font size bigger smaller zoom"
|
||||
}
|
||||
|
||||
@@ -549,6 +549,12 @@ fn settings_search_entries() -> &'static [SearchEntry] {
|
||||
title: SettingsFontLigatures,
|
||||
keywords: SettingsSearchFontLigaturesKeywords,
|
||||
},
|
||||
#[cfg(target_os = "macos")]
|
||||
SearchEntry {
|
||||
section: Appearance,
|
||||
title: SettingsFontThicken,
|
||||
keywords: SettingsSearchFontThickenKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: Appearance,
|
||||
title: SettingsCursorShape,
|
||||
@@ -920,6 +926,7 @@ impl SearchEntry {
|
||||
L10nKey::SettingsItalicFont => "font_family_italic",
|
||||
L10nKey::SettingsUiFontFamily => "ui_font_family",
|
||||
L10nKey::SettingsFontLigatures => "font_features",
|
||||
L10nKey::SettingsFontThicken => "font_thicken",
|
||||
L10nKey::SettingsOpacity => "window_opacity",
|
||||
L10nKey::SettingsBlur => "window_blur",
|
||||
L10nKey::SettingsBackdrop => "window_backdrop",
|
||||
@@ -956,6 +963,7 @@ impl SearchEntry {
|
||||
L10nKey::SettingsBoldFont => t(L10nKey::SettingsBoldFontDesc),
|
||||
L10nKey::SettingsItalicFont => t(L10nKey::SettingsItalicFontDesc),
|
||||
L10nKey::SettingsFontLigatures => t(L10nKey::SettingsFontLigaturesDesc),
|
||||
L10nKey::SettingsFontThicken => t(L10nKey::SettingsFontThickenDesc),
|
||||
L10nKey::SettingsCursorShape => t(L10nKey::SettingsCursorShapeDesc),
|
||||
L10nKey::SettingsCursorBlink => t(L10nKey::SettingsCursorBlinkDesc),
|
||||
L10nKey::SettingsBackgroundImage => t(L10nKey::SettingsBackgroundImageDesc),
|
||||
@@ -1123,6 +1131,7 @@ impl SearchEntry {
|
||||
cfg.working_directory.path != defaults.working_directory.path
|
||||
}
|
||||
L10nKey::SettingsFontLigatures => cfg.font_features != defaults.font_features,
|
||||
L10nKey::SettingsFontThicken => cfg.font_thicken != defaults.font_thicken,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -3488,6 +3497,7 @@ impl Tty7App {
|
||||
let cfg = cx.global::<Config>();
|
||||
let cursor_style = cfg.cursor_style;
|
||||
let cursor_blink = cfg.cursor_blink;
|
||||
let font_thicken = cfg.font_thicken;
|
||||
let font_ligatures = cfg.font_features.as_ref().is_some_and(|features| {
|
||||
features.is_calt_enabled() == Some(true)
|
||||
|| features
|
||||
@@ -3591,6 +3601,19 @@ impl Tty7App {
|
||||
.checked(font_ligatures)
|
||||
.on_click(cx.listener(|this, on: &bool, _w, cx| this.set_font_ligatures(*on, cx)))
|
||||
.into_any_element();
|
||||
// macOS alone dilates glyph strokes, so elsewhere there is no row.
|
||||
let thicken_row = cfg!(target_os = "macos").then(|| {
|
||||
let thicken_switch = crate::ui::theme::switch("font-thicken", cx)
|
||||
.checked(font_thicken)
|
||||
.on_click(cx.listener(|this, on: &bool, _w, cx| this.set_font_thicken(*on, cx)))
|
||||
.into_any_element();
|
||||
self.settings_row(
|
||||
t(L10nKey::SettingsFontThicken),
|
||||
t(L10nKey::SettingsFontThickenDesc),
|
||||
thicken_switch,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let cursor_idx = match cursor_style {
|
||||
CursorStyle::Block => 0,
|
||||
@@ -3682,6 +3705,7 @@ impl Tty7App {
|
||||
ligature_switch,
|
||||
cx,
|
||||
))
|
||||
.when_some(thicken_row, |v, row| v.child(row))
|
||||
.child(self.section_rule(cx))
|
||||
.child(self.section_header(t(L10nKey::SettingsCursor), cx))
|
||||
.child(self.settings_row(
|
||||
|
||||
Reference in New Issue
Block a user