gpui/text_system/
font_features.rs1use std::borrow::Cow;
2use std::sync::Arc;
3
4use schemars::{JsonSchema, json_schema};
5
6#[derive(Default, Clone, Eq, PartialEq, Hash)]
34pub struct FontFeatures(pub Arc<Vec<(String, u32)>>);
35
36impl FontFeatures {
37 pub fn disable_ligatures() -> Self {
41 Self(Arc::new(vec![("calt".into(), 0)]))
42 }
43
44 pub fn tag_value_list(&self) -> &[(String, u32)] {
49 self.0.as_slice()
50 }
51
52 pub fn is_calt_enabled(&self) -> Option<bool> {
56 self.0
57 .iter()
58 .find(|(feature, _)| feature == "calt")
59 .map(|(_, value)| *value == 1)
60 }
61}
62
63impl std::fmt::Debug for FontFeatures {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 let mut debug = f.debug_struct("FontFeatures");
66 for (tag, value) in self.tag_value_list() {
67 debug.field(tag, value);
68 }
69
70 debug.finish()
71 }
72}
73
74#[derive(Debug, serde::Serialize, serde::Deserialize)]
75#[serde(untagged)]
76enum FeatureValue {
77 Bool(bool),
78 Number(serde_json::Number),
79}
80
81impl<'de> serde::Deserialize<'de> for FontFeatures {
82 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
83 where
84 D: serde::Deserializer<'de>,
85 {
86 use serde::de::{MapAccess, Visitor};
87 use std::fmt;
88
89 struct FontFeaturesVisitor;
90
91 impl<'de> Visitor<'de> for FontFeaturesVisitor {
92 type Value = FontFeatures;
93
94 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
95 formatter.write_str("a map of font features")
96 }
97
98 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
99 where
100 M: MapAccess<'de>,
101 {
102 let mut feature_list = Vec::new();
103
104 while let Some((key, value)) =
105 access.next_entry::<String, Option<FeatureValue>>()?
106 {
107 if !is_valid_feature_tag(&key) {
108 log::error!("Incorrect font feature tag: {}", key);
109 continue;
110 }
111 if let Some(value) = value {
112 match value {
113 FeatureValue::Bool(enable) => {
114 if enable {
115 feature_list.push((key, 1));
116 } else {
117 feature_list.push((key, 0));
118 }
119 }
120 FeatureValue::Number(value) => {
121 if value.is_u64() {
122 feature_list.push((key, value.as_u64().unwrap() as u32));
123 } else {
124 log::error!(
125 "Incorrect font feature value {} for feature tag {}",
126 value,
127 key
128 );
129 continue;
130 }
131 }
132 }
133 }
134 }
135
136 Ok(FontFeatures(Arc::new(feature_list)))
137 }
138 }
139
140 let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
141 Ok(features)
142 }
143}
144
145impl serde::Serialize for FontFeatures {
146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147 where
148 S: serde::Serializer,
149 {
150 use serde::ser::SerializeMap;
151
152 let mut map = serializer.serialize_map(None)?;
153
154 for (tag, value) in self.tag_value_list() {
155 map.serialize_entry(tag, value)?;
156 }
157
158 map.end()
159 }
160}
161
162impl JsonSchema for FontFeatures {
163 fn schema_name() -> Cow<'static, str> {
164 "FontFeatures".into()
165 }
166
167 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
168 json_schema!({
169 "type": "object",
170 "patternProperties": {
171 "[0-9a-zA-Z]{4}$": {
172 "type": ["boolean", "integer"],
173 "minimum": 0,
174 "multipleOf": 1
175 }
176 },
177 "additionalProperties": false
178 })
179 }
180}
181
182fn is_valid_feature_tag(tag: &str) -> bool {
183 tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())
184}