From 80caa74866f6a8bb073aed78f8a6f11bbd76127e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Fri, 17 May 2024 05:27:55 +0800 Subject: [PATCH] Support setting font feature values (#11898) Now (on `macOS` and `Windows`) we can set font feature value: ```rust "buffer_font_features": { "cv01": true, "cv03": 3, "cv09": 1, "VSAH": 7, "VSAJ": 8 } ``` And one can still use `"cv01": true`. https://github.com/zed-industries/zed/assets/14981363/3e3fcf4f-abdb-4d9e-a0a6-71dc24a515c2 Release Notes: - Added font feature values, now you can set font features like `"cv01": 7`. --------- Co-authored-by: Mikayla --- crates/gpui/src/platform/mac/open_type.rs | 7 +- .../gpui/src/platform/windows/direct_write.rs | 31 +- crates/gpui/src/text_system/font_features.rs | 380 +++++++----------- 3 files changed, 153 insertions(+), 265 deletions(-) diff --git a/crates/gpui/src/platform/mac/open_type.rs b/crates/gpui/src/platform/mac/open_type.rs index 9948162c86..b7c49cdfcd 100644 --- a/crates/gpui/src/platform/mac/open_type.rs +++ b/crates/gpui/src/platform/mac/open_type.rs @@ -30,14 +30,11 @@ pub fn apply_features(font: &mut Font, features: &FontFeatures) { let native_font = font.native_font(); let mut feature_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - for (tag, enable) in features.tag_value_list() { - if !enable { - continue; - } + for (tag, value) in features.tag_value_list() { let keys = [kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue]; let values = [ CFString::new(&tag).as_CFTypeRef(), - CFNumber::from(1).as_CFTypeRef(), + CFNumber::from(*value as i32).as_CFTypeRef(), ]; let dict = CFDictionaryCreate( kCFAllocatorDefault, diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index 83e0605025..99d9fd0a6f 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -1201,26 +1201,26 @@ fn apply_font_features( // All of these features are enabled by default by DirectWrite. // If you want to (and can) peek into the source of DirectWrite - let mut feature_liga = make_direct_write_feature("liga", true); - let mut feature_clig = make_direct_write_feature("clig", true); - let mut feature_calt = make_direct_write_feature("calt", true); + let mut feature_liga = make_direct_write_feature("liga", 1); + let mut feature_clig = make_direct_write_feature("clig", 1); + let mut feature_calt = make_direct_write_feature("calt", 1); - for (tag, enable) in tag_values { - if tag == *"liga" && !enable { + for (tag, value) in tag_values { + if tag.as_str() == "liga" && *value == 0 { feature_liga.parameter = 0; continue; } - if tag == *"clig" && !enable { + if tag.as_str() == "clig" && *value == 0 { feature_clig.parameter = 0; continue; } - if tag == *"calt" && !enable { + if tag.as_str() == "calt" && *value == 0 { feature_calt.parameter = 0; continue; } unsafe { - direct_write_features.AddFontFeature(make_direct_write_feature(&tag, enable))?; + direct_write_features.AddFontFeature(make_direct_write_feature(&tag, *value))?; } } unsafe { @@ -1233,18 +1233,11 @@ fn apply_font_features( } #[inline] -fn make_direct_write_feature(feature_name: &str, enable: bool) -> DWRITE_FONT_FEATURE { +fn make_direct_write_feature(feature_name: &str, parameter: u32) -> DWRITE_FONT_FEATURE { let tag = make_direct_write_tag(feature_name); - if enable { - DWRITE_FONT_FEATURE { - nameTag: tag, - parameter: 1, - } - } else { - DWRITE_FONT_FEATURE { - nameTag: tag, - parameter: 0, - } + DWRITE_FONT_FEATURE { + nameTag: tag, + parameter, } } diff --git a/crates/gpui/src/text_system/font_features.rs b/crates/gpui/src/text_system/font_features.rs index 3202b6f60f..ef6d76481a 100644 --- a/crates/gpui/src/text_system/font_features.rs +++ b/crates/gpui/src/text_system/font_features.rs @@ -1,246 +1,144 @@ -use crate::SharedString; -use itertools::Itertools; -use schemars::{ - schema::{InstanceType, Schema, SchemaObject, SingleOrVec}, - JsonSchema, -}; +use std::sync::Arc; -macro_rules! create_definitions { - ($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => { +use schemars::schema::{InstanceType, SchemaObject}; - /// The OpenType features that can be configured for a given font. - #[derive(Default, Clone, Eq, PartialEq, Hash)] - pub struct FontFeatures { - enabled: u64, - disabled: u64, - other_enabled: SharedString, - other_disabled: SharedString, - } +/// The OpenType features that can be configured for a given font. +#[derive(Default, Clone, Eq, PartialEq, Hash)] +pub struct FontFeatures(pub Arc>); - impl FontFeatures { - $( - /// Get the current value of the corresponding OpenType feature - pub fn $name(&self) -> Option { - if (self.enabled & (1 << $idx)) != 0 { - Some(true) - } else if (self.disabled & (1 << $idx)) != 0 { - Some(false) - } else { - None - } - } - )* - - /// Get the tag name list of the font OpenType features - /// only enabled or disabled features are returned - pub fn tag_value_list(&self) -> Vec<(String, bool)> { - let mut result = Vec::new(); - $( - { - let value = if (self.enabled & (1 << $idx)) != 0 { - Some(true) - } else if (self.disabled & (1 << $idx)) != 0 { - Some(false) - } else { - None - }; - if let Some(enable) = value { - let tag_name = stringify!($name).to_owned(); - result.push((tag_name, enable)); - } - } - )* - { - for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() { - result.push((name.collect::(), true)); - } - for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() { - result.push((name.collect::(), false)); - } - } - result - } - } - - impl std::fmt::Debug for FontFeatures { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug = f.debug_struct("FontFeatures"); - $( - if let Some(value) = self.$name() { - 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::().as_str(), &true); - } - for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() { - debug.field(name.collect::().as_str(), &false); - } - } - debug.finish() - } - } - - impl<'de> serde::Deserialize<'de> for FontFeatures { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::{MapAccess, Visitor}; - use std::fmt; - - struct FontFeaturesVisitor; - - impl<'de> Visitor<'de> for FontFeaturesVisitor { - type Value = FontFeatures; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map of font features") - } - - fn visit_map(self, mut access: M) -> Result - 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::>()? { - 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)?; - Ok(features) - } - } - - impl serde::Serialize for FontFeatures { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeMap; - - let mut map = serializer.serialize_map(None)?; - - $( - { - let feature = stringify!($name); - if let Some(value) = self.$name() { - map.serialize_entry(feature, &value)?; - } - } - )* - - #[cfg(target_os = "windows")] - { - for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() { - map.serialize_entry(name.collect::().as_str(), &true)?; - } - for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() { - map.serialize_entry(name.collect::().as_str(), &false)?; - } - } - - map.end() - } - } - - impl JsonSchema for FontFeatures { - fn schema_name() -> String { - "FontFeatures".into() - } - - fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema { - let mut schema = SchemaObject::default(); - let properties = &mut schema.object().properties; - let feature_schema = Schema::Object(SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))), - ..Default::default() - }); - - $( - properties.insert(stringify!($name).to_owned(), feature_schema.clone()); - )* - - schema.into() - } - } - }; +impl FontFeatures { + /// Get the tag name list of the font OpenType features + /// only enabled or disabled features are returned + pub fn tag_value_list(&self) -> &[(String, u32)] { + &self.0.as_slice() + } } -create_definitions!( - (calt, 0), - (case, 1), - (cpsp, 2), - (frac, 3), - (liga, 4), - (onum, 5), - (ordn, 6), - (pnum, 7), - (ss01, 8), - (ss02, 9), - (ss03, 10), - (ss04, 11), - (ss05, 12), - (ss06, 13), - (ss07, 14), - (ss08, 15), - (ss09, 16), - (ss10, 17), - (ss11, 18), - (ss12, 19), - (ss13, 20), - (ss14, 21), - (ss15, 22), - (ss16, 23), - (ss17, 24), - (ss18, 25), - (ss19, 26), - (ss20, 27), - (subs, 28), - (sups, 29), - (swsh, 30), - (titl, 31), - (tnum, 32), - (zero, 33), -); +impl std::fmt::Debug for FontFeatures { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("FontFeatures"); + for (tag, value) in self.tag_value_list() { + debug.field(tag, value); + } + + debug.finish() + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +enum FeatureValue { + Bool(bool), + Number(serde_json::Number), +} + +impl<'de> serde::Deserialize<'de> for FontFeatures { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{MapAccess, Visitor}; + use std::fmt; + + struct FontFeaturesVisitor; + + impl<'de> Visitor<'de> for FontFeaturesVisitor { + type Value = FontFeatures; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map of font features") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut feature_list = Vec::new(); + + while let Some((key, value)) = + access.next_entry::>()? + { + if key.len() != 4 && !key.is_ascii() { + log::error!("Incorrect font feature tag: {}", key); + continue; + } + if let Some(value) = value { + match value { + FeatureValue::Bool(enable) => { + if enable { + feature_list.push((key, 1)); + } else { + feature_list.push((key, 0)); + } + } + FeatureValue::Number(value) => { + if value.is_u64() { + feature_list.push((key, value.as_u64().unwrap() as u32)); + } else { + log::error!( + "Incorrect font feature value {} for feature tag {}", + value, + key + ); + continue; + } + } + } + } + } + + Ok(FontFeatures(Arc::new(feature_list))) + } + } + + let features = deserializer.deserialize_map(FontFeaturesVisitor)?; + Ok(features) + } +} + +impl serde::Serialize for FontFeatures { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + + let mut map = serializer.serialize_map(None)?; + + for (tag, value) in self.tag_value_list() { + map.serialize_entry(tag, value)?; + } + + map.end() + } +} + +impl schemars::JsonSchema for FontFeatures { + fn schema_name() -> String { + "FontFeatures".into() + } + + fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + let mut schema = SchemaObject::default(); + schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new( + InstanceType::Object, + ))); + { + let mut property = SchemaObject::default(); + property.instance_type = Some(schemars::schema::SingleOrVec::Vec(vec![ + InstanceType::Boolean, + InstanceType::Integer, + ])); + { + let mut number_constraints = property.number(); + number_constraints.multiple_of = Some(1.0); + number_constraints.minimum = Some(0.0); + } + schema + .object() + .pattern_properties + .insert("[0-9a-zA-Z]{4}$".into(), property.into()); + } + schema.into() + } +}