Set `*_font_fallbacks` default to `None` (#16941)

张小白 created

In the current `default.json`, `*_font_fallbacks=[]`, which results in
the `fallbacks` value in the `Font` struct always being `Some(...)`.

This PR introduces the following improvements:
1. Changed `*_font_fallbacks = []` to `*_font_fallbacks = null` in
`default.json`.
2. Enhanced the macOS and Windows implementations.

Release Notes:

- N/A

Change summary

assets/settings/default.json                     |  4 
crates/gpui/src/platform/mac/open_type.rs        | 82 +++++++++-------
crates/gpui/src/platform/windows/direct_write.rs | 86 ++++++++---------
3 files changed, 89 insertions(+), 83 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -28,7 +28,7 @@
   "buffer_font_family": "Zed Plex Mono",
   // Set the buffer text's font fallbacks, this will be merged with
   // the platform's default fallbacks.
-  "buffer_font_fallbacks": [],
+  "buffer_font_fallbacks": null,
   // The OpenType features to enable for text in the editor.
   "buffer_font_features": {
     // Disable ligatures:
@@ -54,7 +54,7 @@
   "ui_font_family": "Zed Plex Sans",
   // Set the UI's font fallbacks, this will be merged with the platform's
   // default font fallbacks.
-  "ui_font_fallbacks": [],
+  "ui_font_fallbacks": null,
   // The OpenType features to enable for text in the UI
   "ui_font_features": {
     // Disable ligatures:

crates/gpui/src/platform/mac/open_type.rs 🔗

@@ -35,50 +35,25 @@ pub fn apply_features_and_fallbacks(
     fallbacks: Option<&FontFallbacks>,
 ) -> anyhow::Result<()> {
     unsafe {
-        let fallback_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
-
+        let mut keys = vec![kCTFontFeatureSettingsAttribute];
+        let mut values = vec![generate_feature_array(features)];
         if let Some(fallbacks) = fallbacks {
-            for user_fallback in fallbacks.fallback_list() {
-                let name = CFString::from(user_fallback.as_str());
-                let fallback_desc =
-                    CTFontDescriptorCreateWithNameAndSize(name.as_concrete_TypeRef(), 0.0);
-                CFArrayAppendValue(fallback_array, fallback_desc as _);
-                CFRelease(fallback_desc as _);
+            if !fallbacks.fallback_list().is_empty() {
+                keys.push(kCTFontCascadeListAttribute);
+                values.push(generate_fallback_array(
+                    fallbacks,
+                    font.native_font().as_concrete_TypeRef(),
+                ));
             }
         }
-
-        {
-            let preferred_languages: CFArray<CFString> =
-                CFArray::wrap_under_create_rule(CFLocaleCopyPreferredLanguages());
-
-            let default_fallbacks = CTFontCopyDefaultCascadeListForLanguages(
-                font.native_font().as_concrete_TypeRef(),
-                preferred_languages.as_concrete_TypeRef(),
-            );
-            let default_fallbacks: CFArray<CTFontDescriptor> =
-                CFArray::wrap_under_create_rule(default_fallbacks);
-
-            default_fallbacks
-                .iter()
-                .filter(|desc| desc.font_path().is_some())
-                .map(|desc| {
-                    CFArrayAppendValue(fallback_array, desc.as_concrete_TypeRef() as _);
-                });
-        }
-
-        let feature_array = generate_feature_array(features);
-        let keys = [kCTFontFeatureSettingsAttribute, kCTFontCascadeListAttribute];
-        let values = [feature_array, fallback_array];
         let attrs = CFDictionaryCreate(
             kCFAllocatorDefault,
             keys.as_ptr() as _,
             values.as_ptr() as _,
-            2,
+            keys.len() as isize,
             &kCFTypeDictionaryKeyCallBacks,
             &kCFTypeDictionaryValueCallBacks,
         );
-        CFRelease(feature_array as *const _ as _);
-        CFRelease(fallback_array as *const _ as _);
         let new_descriptor = CTFontDescriptorCreateWithAttributes(attrs);
         CFRelease(attrs as _);
         let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
@@ -97,8 +72,7 @@ pub fn apply_features_and_fallbacks(
 
 fn generate_feature_array(features: &FontFeatures) -> CFMutableArrayRef {
     unsafe {
-        let mut feature_array =
-            CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+        let feature_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
         for (tag, value) in features.tag_value_list() {
             let keys = [kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue];
             let values = [
@@ -121,6 +95,42 @@ fn generate_feature_array(features: &FontFeatures) -> CFMutableArrayRef {
     }
 }
 
+fn generate_fallback_array(fallbacks: &FontFallbacks, font_ref: CTFontRef) -> CFMutableArrayRef {
+    unsafe {
+        let fallback_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+        for user_fallback in fallbacks.fallback_list() {
+            let name = CFString::from(user_fallback.as_str());
+            let fallback_desc =
+                CTFontDescriptorCreateWithNameAndSize(name.as_concrete_TypeRef(), 0.0);
+            CFArrayAppendValue(fallback_array, fallback_desc as _);
+            CFRelease(fallback_desc as _);
+        }
+        append_system_fallbacks(fallback_array, font_ref);
+        fallback_array
+    }
+}
+
+fn append_system_fallbacks(fallback_array: CFMutableArrayRef, font_ref: CTFontRef) {
+    unsafe {
+        let preferred_languages: CFArray<CFString> =
+            CFArray::wrap_under_create_rule(CFLocaleCopyPreferredLanguages());
+
+        let default_fallbacks = CTFontCopyDefaultCascadeListForLanguages(
+            font_ref,
+            preferred_languages.as_concrete_TypeRef(),
+        );
+        let default_fallbacks: CFArray<CTFontDescriptor> =
+            CFArray::wrap_under_create_rule(default_fallbacks);
+
+        default_fallbacks
+            .iter()
+            .filter(|desc| desc.font_path().is_some())
+            .map(|desc| {
+                CFArrayAppendValue(fallback_array, desc.as_concrete_TypeRef() as _);
+            });
+    }
+}
+
 #[link(name = "CoreText", kind = "framework")]
 extern "C" {
     static kCTFontOpenTypeFeatureTag: CFStringRef;

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

@@ -275,54 +275,52 @@ impl DirectWriteState {
 
     fn generate_font_fallbacks(
         &self,
-        fallbacks: Option<&FontFallbacks>,
+        fallbacks: &FontFallbacks,
     ) -> Result<Option<IDWriteFontFallback>> {
-        if fallbacks.is_some_and(|fallbacks| fallbacks.fallback_list().is_empty()) {
+        if fallbacks.fallback_list().is_empty() {
             return Ok(None);
         }
         unsafe {
             let builder = self.components.factory.CreateFontFallbackBuilder()?;
             let font_set = &self.system_font_collection.GetFontSet()?;
-            if let Some(fallbacks) = fallbacks {
-                for family_name in fallbacks.fallback_list() {
-                    let Some(fonts) = font_set
-                        .GetMatchingFonts(
-                            &HSTRING::from(family_name),
-                            DWRITE_FONT_WEIGHT_NORMAL,
-                            DWRITE_FONT_STRETCH_NORMAL,
-                            DWRITE_FONT_STYLE_NORMAL,
-                        )
-                        .log_err()
-                    else {
-                        continue;
-                    };
-                    if fonts.GetFontCount() == 0 {
-                        log::error!("No matching font found for {}", family_name);
-                        continue;
-                    }
-                    let font = fonts.GetFontFaceReference(0)?.CreateFontFace()?;
-                    let mut count = 0;
-                    font.GetUnicodeRanges(None, &mut count).ok();
-                    if count == 0 {
-                        continue;
-                    }
-                    let mut unicode_ranges = vec![DWRITE_UNICODE_RANGE::default(); count as usize];
-                    let Some(_) = font
-                        .GetUnicodeRanges(Some(&mut unicode_ranges), &mut count)
-                        .log_err()
-                    else {
-                        continue;
-                    };
-                    let target_family_name = HSTRING::from(family_name);
-                    builder.AddMapping(
-                        &unicode_ranges,
-                        &[target_family_name.as_ptr()],
-                        None,
-                        None,
-                        None,
-                        1.0,
-                    )?;
+            for family_name in fallbacks.fallback_list() {
+                let Some(fonts) = font_set
+                    .GetMatchingFonts(
+                        &HSTRING::from(family_name),
+                        DWRITE_FONT_WEIGHT_NORMAL,
+                        DWRITE_FONT_STRETCH_NORMAL,
+                        DWRITE_FONT_STYLE_NORMAL,
+                    )
+                    .log_err()
+                else {
+                    continue;
+                };
+                if fonts.GetFontCount() == 0 {
+                    log::error!("No matching font found for {}", family_name);
+                    continue;
                 }
+                let font = fonts.GetFontFaceReference(0)?.CreateFontFace()?;
+                let mut count = 0;
+                font.GetUnicodeRanges(None, &mut count).ok();
+                if count == 0 {
+                    continue;
+                }
+                let mut unicode_ranges = vec![DWRITE_UNICODE_RANGE::default(); count as usize];
+                let Some(_) = font
+                    .GetUnicodeRanges(Some(&mut unicode_ranges), &mut count)
+                    .log_err()
+                else {
+                    continue;
+                };
+                let target_family_name = HSTRING::from(family_name);
+                builder.AddMapping(
+                    &unicode_ranges,
+                    &[target_family_name.as_ptr()],
+                    None,
+                    None,
+                    None,
+                    1.0,
+                )?;
             }
             let system_fallbacks = self.components.factory.GetSystemFontFallback()?;
             builder.AddMappings(&system_fallbacks)?;
@@ -378,10 +376,8 @@ impl DirectWriteState {
             else {
                 continue;
             };
-            let fallbacks = self
-                .generate_font_fallbacks(font_fallbacks)
-                .log_err()
-                .unwrap_or_default();
+            let fallbacks = font_fallbacks
+                .and_then(|fallbacks| self.generate_font_fallbacks(fallbacks).log_err().flatten());
             let font_info = FontInfo {
                 font_family: family_name.to_owned(),
                 font_face,