windows: Support all `OpenType` font features (#10756)

张小白 and Mikayla Maki created

Release Notes:

- Added support for all `OpenType` font features to DirectWrite.



https://github.com/zed-industries/zed/assets/14981363/cb2848cd-9178-4d87-881a-54dc646b2b61

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/assistant/src/assistant_panel.rs             |  2 
crates/assistant/src/completion_provider/open_ai.rs |  2 
crates/collab_ui/src/chat_panel/message_editor.rs   |  2 
crates/collab_ui/src/collab_panel.rs                |  2 
crates/editor/src/editor.rs                         |  6 
crates/extensions_ui/src/extensions_ui.rs           |  2 
crates/gpui/src/platform/cosmic_text/text_system.rs |  4 
crates/gpui/src/platform/mac/open_type.rs           |  2 
crates/gpui/src/platform/mac/text_system.rs         | 10 +
crates/gpui/src/style.rs                            |  2 
crates/gpui/src/text_system/font_features.rs        | 86 ++++++++++++++
crates/outline/src/outline.rs                       |  2 
crates/search/src/buffer_search.rs                  |  2 
crates/search/src/project_search.rs                 |  2 
crates/terminal_view/src/terminal_element.rs        |  3 
crates/theme/src/settings.rs                        |  8 
crates/ui_text_field/src/ui_text_field.rs           |  2 
17 files changed, 114 insertions(+), 25 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -2873,7 +2873,7 @@ impl InlineAssistant {
                 cx.theme().colors().text
             },
             font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
+            font_features: settings.ui_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/assistant/src/completion_provider/open_ai.rs 🔗

@@ -241,7 +241,7 @@ impl AuthenticationPrompt {
         let text_style = TextStyle {
             color: cx.theme().colors().text,
             font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
+            font_features: settings.ui_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -522,7 +522,7 @@ impl Render for MessageEditor {
                 cx.theme().colors().text
             },
             font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
+            font_features: settings.ui_font.features.clone(),
             font_size: TextSize::Small.rems(cx).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/collab_ui/src/collab_panel.rs 🔗

@@ -2171,7 +2171,7 @@ impl CollabPanel {
                 cx.theme().colors().text
             },
             font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
+            font_features: settings.ui_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/editor/src/editor.rs 🔗

@@ -10340,7 +10340,7 @@ impl Render for Editor {
             EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
                 color: cx.theme().colors().editor_foreground,
                 font_family: settings.ui_font.family.clone(),
-                font_features: settings.ui_font.features,
+                font_features: settings.ui_font.features.clone(),
                 font_size: rems(0.875).into(),
                 font_weight: FontWeight::NORMAL,
                 font_style: FontStyle::Normal,
@@ -10353,7 +10353,7 @@ impl Render for Editor {
             EditorMode::Full => TextStyle {
                 color: cx.theme().colors().editor_foreground,
                 font_family: settings.buffer_font.family.clone(),
-                font_features: settings.buffer_font.features,
+                font_features: settings.buffer_font.features.clone(),
                 font_size: settings.buffer_font_size(cx).into(),
                 font_weight: FontWeight::NORMAL,
                 font_style: FontStyle::Normal,
@@ -10778,7 +10778,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
         let theme_settings = ThemeSettings::get_global(cx);
         text_style.font_family = theme_settings.buffer_font.family.clone();
         text_style.font_style = theme_settings.buffer_font.style;
-        text_style.font_features = theme_settings.buffer_font.features;
+        text_style.font_features = theme_settings.buffer_font.features.clone();
         text_style.font_weight = theme_settings.buffer_font.weight;
 
         let multi_line_diagnostic = diagnostic.message.contains('\n');

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -739,7 +739,7 @@ impl ExtensionsPage {
                 cx.theme().colors().text
             },
             font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features,
+            font_features: settings.ui_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/gpui/src/platform/cosmic_text/text_system.rs 🔗

@@ -90,7 +90,7 @@ impl PlatformTextSystem for CosmicTextSystem {
         let candidates = if let Some(font_ids) = state.font_ids_by_family_cache.get(&font.family) {
             font_ids.as_slice()
         } else {
-            let font_ids = state.load_family(&font.family, font.features)?;
+            let font_ids = state.load_family(&font.family, &font.features)?;
             state
                 .font_ids_by_family_cache
                 .insert(font.family.clone(), font_ids);
@@ -211,7 +211,7 @@ impl CosmicTextSystemState {
     fn load_family(
         &mut self,
         name: &str,
-        _features: FontFeatures,
+        _features: &FontFeatures,
     ) -> Result<SmallVec<[FontId; 4]>> {
         // TODO: Determine the proper system UI font.
         let name = if name == ".SystemUIFont" {

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

@@ -107,7 +107,7 @@ const kTypographicExtrasType: i32 = 14;
 const kVerticalFractionsSelector: i32 = 1;
 const kVerticalPositionType: i32 = 10;
 
-pub fn apply_features(font: &mut Font, features: FontFeatures) {
+pub fn apply_features(font: &mut Font, features: &FontFeatures) {
     // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
     // for a reference implementation.
     toggle_open_type_feature(

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

@@ -123,12 +123,12 @@ impl PlatformTextSystem for MacTextSystem {
             let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
             let font_key = FontKey {
                 font_family: font.family.clone(),
-                font_features: font.features,
+                font_features: font.features.clone(),
             };
             let candidates = if let Some(font_ids) = lock.font_ids_by_font_key.get(&font_key) {
                 font_ids.as_slice()
             } else {
-                let font_ids = lock.load_family(&font.family, font.features)?;
+                let font_ids = lock.load_family(&font.family, &font.features)?;
                 lock.font_ids_by_font_key.insert(font_key.clone(), font_ids);
                 lock.font_ids_by_font_key[&font_key].as_ref()
             };
@@ -219,7 +219,11 @@ impl MacTextSystemState {
         Ok(())
     }
 
-    fn load_family(&mut self, name: &str, features: FontFeatures) -> Result<SmallVec<[FontId; 4]>> {
+    fn load_family(
+        &mut self,
+        name: &str,
+        features: &FontFeatures,
+    ) -> Result<SmallVec<[FontId; 4]>> {
         let name = if name == ".SystemUIFont" {
             ".AppleSystemUIFont"
         } else {

crates/gpui/src/style.rs 🔗

@@ -262,7 +262,7 @@ impl TextStyle {
     pub fn font(&self) -> Font {
         Font {
             family: self.font_family.clone(),
-            features: self.font_features,
+            features: self.font_features.clone(),
             weight: self.font_weight,
             style: self.font_style,
         }

crates/gpui/src/text_system/font_features.rs 🔗

@@ -1,3 +1,7 @@
+#[cfg(target_os = "windows")]
+use crate::SharedString;
+#[cfg(target_os = "windows")]
+use itertools::Itertools;
 use schemars::{
     schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
     JsonSchema,
@@ -7,10 +11,14 @@ macro_rules! create_definitions {
     ($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
 
         /// The OpenType features that can be configured for a given font.
-        #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
+        #[derive(Default, Clone, Eq, PartialEq, Hash)]
         pub struct FontFeatures {
             enabled: u64,
             disabled: u64,
+            #[cfg(target_os = "windows")]
+            other_enabled: SharedString,
+            #[cfg(target_os = "windows")]
+            other_disabled: SharedString,
         }
 
         impl FontFeatures {
@@ -47,6 +55,14 @@ macro_rules! create_definitions {
                         }
                     }
                 )*
+                {
+                    for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
+                        result.push((name.collect::<String>(), true));
+                    }
+                    for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
+                        result.push((name.collect::<String>(), false));
+                    }
+                }
                 result
             }
         }
@@ -59,6 +75,15 @@ macro_rules! create_definitions {
                         debug.field(stringify!($name), &value);
                     };
                 )*
+                #[cfg(target_os = "windows")]
+                {
+                    for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
+                        debug.field(name.collect::<String>().as_str(), &true);
+                    }
+                    for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
+                        debug.field(name.collect::<String>().as_str(), &false);
+                    }
+                }
                 debug.finish()
             }
         }
@@ -80,6 +105,7 @@ macro_rules! create_definitions {
                         formatter.write_str("a map of font features")
                     }
 
+                    #[cfg(not(target_os = "windows"))]
                     fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
                     where
                         M: MapAccess<'de>,
@@ -100,6 +126,54 @@ macro_rules! create_definitions {
                         }
                         Ok(FontFeatures { enabled, disabled })
                     }
+
+                    #[cfg(target_os = "windows")]
+                    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+                    where
+                        M: MapAccess<'de>,
+                    {
+                        let mut enabled: u64 = 0;
+                        let mut disabled: u64 = 0;
+                        let mut other_enabled = "".to_owned();
+                        let mut other_disabled = "".to_owned();
+
+                        while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
+                            let idx = match key.as_str() {
+                                $(stringify!($name) => Some($idx),)*
+                                other_feature => {
+                                    if other_feature.len() != 4 || !other_feature.is_ascii() {
+                                        log::error!("Incorrect feature name: {}", other_feature);
+                                        continue;
+                                    }
+                                    None
+                                },
+                            };
+                            if let Some(idx) = idx {
+                                match value {
+                                    Some(true) => enabled |= 1 << idx,
+                                    Some(false) => disabled |= 1 << idx,
+                                    None => {}
+                                };
+                            } else {
+                                match value {
+                                    Some(true) => other_enabled.push_str(key.as_str()),
+                                    Some(false) => other_disabled.push_str(key.as_str()),
+                                    None => {}
+                                };
+                            }
+                        }
+                        let other_enabled = if other_enabled.is_empty() {
+                            "".into()
+                        } else {
+                            other_enabled.into()
+                        };
+                        let other_disabled = if other_disabled.is_empty() {
+                            "".into()
+                        } else {
+                            other_disabled.into()
+                        };
+                        Ok(FontFeatures { enabled, disabled, other_enabled, other_disabled })
+                    }
                 }
 
                 let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
@@ -125,6 +199,16 @@ macro_rules! create_definitions {
                     }
                 )*
 
+                #[cfg(target_os = "windows")]
+                {
+                    for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
+                        map.serialize_entry(name.collect::<String>().as_str(), &true)?;
+                    }
+                    for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
+                        map.serialize_entry(name.collect::<String>().as_str(), &false)?;
+                    }
+                }
+
                 map.end()
             }
         }

crates/outline/src/outline.rs 🔗

@@ -274,7 +274,7 @@ impl PickerDelegate for OutlineViewDelegate {
         let text_style = TextStyle {
             color: cx.theme().colors().text,
             font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
+            font_features: settings.buffer_font.features.clone(),
             font_size: settings.buffer_font_size(cx).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/search/src/buffer_search.rs 🔗

@@ -114,7 +114,7 @@ impl BufferSearchBar {
                 color
             },
             font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
+            font_features: settings.buffer_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/search/src/project_search.rs 🔗

@@ -1307,7 +1307,7 @@ impl ProjectSearchBar {
                 cx.theme().colors().text
             },
             font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
+            font_features: settings.buffer_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,

crates/terminal_view/src/terminal_element.rs 🔗

@@ -578,7 +578,8 @@ impl Element for TerminalElement {
 
                 let font_features = terminal_settings
                     .font_features
-                    .unwrap_or(settings.buffer_font.features);
+                    .clone()
+                    .unwrap_or(settings.buffer_font.features.clone());
 
                 let line_height = terminal_settings.line_height.value();
                 let font_size = terminal_settings.font_size;

crates/theme/src/settings.rs 🔗

@@ -325,13 +325,13 @@ impl settings::Settings for ThemeSettings {
             ui_font_size: defaults.ui_font_size.unwrap().into(),
             ui_font: Font {
                 family: defaults.ui_font_family.clone().unwrap().into(),
-                features: defaults.ui_font_features.unwrap(),
+                features: defaults.ui_font_features.clone().unwrap(),
                 weight: Default::default(),
                 style: Default::default(),
             },
             buffer_font: Font {
                 family: defaults.buffer_font_family.clone().unwrap().into(),
-                features: defaults.buffer_font_features.unwrap(),
+                features: defaults.buffer_font_features.clone().unwrap(),
                 weight: FontWeight::default(),
                 style: FontStyle::default(),
             },
@@ -349,14 +349,14 @@ impl settings::Settings for ThemeSettings {
             if let Some(value) = value.buffer_font_family.clone() {
                 this.buffer_font.family = value.into();
             }
-            if let Some(value) = value.buffer_font_features {
+            if let Some(value) = value.buffer_font_features.clone() {
                 this.buffer_font.features = value;
             }
 
             if let Some(value) = value.ui_font_family.clone() {
                 this.ui_font.family = value.into();
             }
-            if let Some(value) = value.ui_font_features {
+            if let Some(value) = value.ui_font_features.clone() {
                 this.ui_font.features = value;
             }
 

crates/ui_text_field/src/ui_text_field.rs 🔗

@@ -123,7 +123,7 @@ impl Render for TextField {
 
         let text_style = TextStyle {
             font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
+            font_features: settings.buffer_font.features.clone(),
             font_size: rems(0.875).into(),
             font_weight: FontWeight::NORMAL,
             font_style: FontStyle::Normal,