windows: Let the OS decide which font to use as the UI font (#10877)

张小白 created

On my computer, I get `Yahei UI`, which makes sense since I'm using a
Chinese operating system, and `Yahei UI` includes Chinese codepoints. On
an English operating system, `Segoe UI` should be used instead.

Edit: I also choose to use the UI font selected by the system as the
fallback font, rather than hard-coding the `Arial` font.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/direct_write.rs | 100 +++++++++++------
1 file changed, 65 insertions(+), 35 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/direct_write.rs 🔗

@@ -16,9 +16,11 @@ use windows::{
             Direct2D::{Common::*, *},
             DirectWrite::*,
             Dxgi::Common::*,
+            Gdi::LOGFONTW,
             Imaging::{D2D::IWICImagingFactory2, *},
         },
         System::{Com::*, SystemServices::LOCALE_NAME_MAX_LENGTH},
+        UI::WindowsAndMessaging::*,
     },
 };
 
@@ -51,6 +53,7 @@ unsafe impl Send for DirectWriteComponent {}
 
 struct DirectWriteState {
     components: DirectWriteComponent,
+    system_ui_font_name: SharedString,
     system_font_collection: IDWriteFontCollection1,
     custom_font_collection: IDWriteFontCollection1,
     fonts: Vec<FontInfo>,
@@ -106,9 +109,11 @@ impl DirectWriteTextSystem {
                 .factory
                 .CreateFontCollectionFromFontSet(&custom_font_set)?
         };
+        let system_ui_font_name = get_system_ui_font_name();
 
         Ok(Self(RwLock::new(DirectWriteState {
             components,
+            system_ui_font_name,
             system_font_collection,
             custom_font_collection,
             fonts: Vec::new(),
@@ -309,53 +314,55 @@ impl DirectWriteState {
     }
 
     fn select_font(&mut self, target_font: &Font) -> FontId {
-        let family_name = if target_font.family == ".SystemUIFont" {
-            // https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
-            // Segoe UI is the Windows font intended for user interface text strings.
-            "Segoe UI"
-        } else {
-            target_font.family.as_ref()
-        };
         unsafe {
-            // try to find target font in custom font collection first
-            self.get_font_id_from_font_collection(
-                family_name,
-                target_font.weight,
-                target_font.style,
-                &target_font.features,
-                false,
-            )
-            .or_else(|| {
-                self.get_font_id_from_font_collection(
-                    family_name,
+            if target_font.family == ".SystemUIFont" {
+                let family = self.system_ui_font_name.clone();
+                self.find_font_id(
+                    family.as_ref(),
                     target_font.weight,
                     target_font.style,
                     &target_font.features,
-                    true,
                 )
-            })
-            .or_else(|| {
-                self.update_system_font_collection();
-                self.get_font_id_from_font_collection(
-                    family_name,
+                .unwrap()
+            } else {
+                self.find_font_id(
+                    target_font.family.as_ref(),
                     target_font.weight,
                     target_font.style,
                     &target_font.features,
-                    true,
                 )
+                .unwrap_or_else(|| {
+                    let family = self.system_ui_font_name.clone();
+                    log::error!("{} not found, use {} instead.", target_font.family, family);
+                    self.get_font_id_from_font_collection(
+                        family.as_ref(),
+                        target_font.weight,
+                        target_font.style,
+                        &target_font.features,
+                        true,
+                    )
+                    .unwrap()
+                })
+            }
+        }
+    }
+
+    unsafe fn find_font_id(
+        &mut self,
+        family_name: &str,
+        weight: FontWeight,
+        style: FontStyle,
+        features: &FontFeatures,
+    ) -> Option<FontId> {
+        // try to find target font in custom font collection first
+        self.get_font_id_from_font_collection(family_name, weight, style, features, false)
+            .or_else(|| {
+                self.get_font_id_from_font_collection(family_name, weight, style, features, true)
             })
             .or_else(|| {
-                log::error!("{} not found, use Arial instead.", family_name);
-                self.get_font_id_from_font_collection(
-                    "Arial",
-                    target_font.weight,
-                    target_font.style,
-                    &target_font.features,
-                    false,
-                )
+                self.update_system_font_collection();
+                self.get_font_id_from_font_collection(family_name, weight, style, features, true)
             })
-            .unwrap()
-        }
     }
 
     fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
@@ -1271,6 +1278,29 @@ fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F {
     }
 }
 
+fn get_system_ui_font_name() -> SharedString {
+    unsafe {
+        let mut info: LOGFONTW = std::mem::zeroed();
+        let font_family = if SystemParametersInfoW(
+            SPI_GETICONTITLELOGFONT,
+            std::mem::size_of::<LOGFONTW>() as u32,
+            Some(&mut info as *mut _ as _),
+            SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
+        )
+        .log_err()
+        .is_none()
+        {
+            // https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
+            // Segoe UI is the Windows font intended for user interface text strings.
+            "Segoe UI".into()
+        } else {
+            String::from_utf16_lossy(&info.lfFaceName).into()
+        };
+        log::info!("Use {} as UI font.", font_family);
+        font_family
+    }
+}
+
 const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
 const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
     r: 1.0,