font_features.rs

  1use std::sync::Arc;
  2
  3use schemars::schema::{InstanceType, SchemaObject};
  4
  5/// The OpenType features that can be configured for a given font.
  6#[derive(Default, Clone, Eq, PartialEq, Hash)]
  7pub struct FontFeatures(pub Arc<Vec<(String, u32)>>);
  8
  9impl FontFeatures {
 10    /// Disables `calt`.
 11    pub fn disable_ligatures() -> Self {
 12        Self(Arc::new(vec![("calt".into(), 0)]))
 13    }
 14
 15    /// Get the tag name list of the font OpenType features
 16    /// only enabled or disabled features are returned
 17    pub fn tag_value_list(&self) -> &[(String, u32)] {
 18        self.0.as_slice()
 19    }
 20
 21    /// Returns whether the `calt` feature is enabled.
 22    ///
 23    /// Returns `None` if the feature is not present.
 24    pub fn is_calt_enabled(&self) -> Option<bool> {
 25        self.0
 26            .iter()
 27            .find(|(feature, _)| feature == "calt")
 28            .map(|(_, value)| *value == 1)
 29    }
 30}
 31
 32impl std::fmt::Debug for FontFeatures {
 33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 34        let mut debug = f.debug_struct("FontFeatures");
 35        for (tag, value) in self.tag_value_list() {
 36            debug.field(tag, value);
 37        }
 38
 39        debug.finish()
 40    }
 41}
 42
 43#[derive(Debug, serde::Serialize, serde::Deserialize)]
 44#[serde(untagged)]
 45enum FeatureValue {
 46    Bool(bool),
 47    Number(serde_json::Number),
 48}
 49
 50impl<'de> serde::Deserialize<'de> for FontFeatures {
 51    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 52    where
 53        D: serde::Deserializer<'de>,
 54    {
 55        use serde::de::{MapAccess, Visitor};
 56        use std::fmt;
 57
 58        struct FontFeaturesVisitor;
 59
 60        impl<'de> Visitor<'de> for FontFeaturesVisitor {
 61            type Value = FontFeatures;
 62
 63            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
 64                formatter.write_str("a map of font features")
 65            }
 66
 67            fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
 68            where
 69                M: MapAccess<'de>,
 70            {
 71                let mut feature_list = Vec::new();
 72
 73                while let Some((key, value)) =
 74                    access.next_entry::<String, Option<FeatureValue>>()?
 75                {
 76                    if !is_valid_feature_tag(&key) {
 77                        log::error!("Incorrect font feature tag: {}", key);
 78                        continue;
 79                    }
 80                    if let Some(value) = value {
 81                        match value {
 82                            FeatureValue::Bool(enable) => {
 83                                if enable {
 84                                    feature_list.push((key, 1));
 85                                } else {
 86                                    feature_list.push((key, 0));
 87                                }
 88                            }
 89                            FeatureValue::Number(value) => {
 90                                if value.is_u64() {
 91                                    feature_list.push((key, value.as_u64().unwrap() as u32));
 92                                } else {
 93                                    log::error!(
 94                                        "Incorrect font feature value {} for feature tag {}",
 95                                        value,
 96                                        key
 97                                    );
 98                                    continue;
 99                                }
100                            }
101                        }
102                    }
103                }
104
105                Ok(FontFeatures(Arc::new(feature_list)))
106            }
107        }
108
109        let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
110        Ok(features)
111    }
112}
113
114impl serde::Serialize for FontFeatures {
115    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
116    where
117        S: serde::Serializer,
118    {
119        use serde::ser::SerializeMap;
120
121        let mut map = serializer.serialize_map(None)?;
122
123        for (tag, value) in self.tag_value_list() {
124            map.serialize_entry(tag, value)?;
125        }
126
127        map.end()
128    }
129}
130
131impl schemars::JsonSchema for FontFeatures {
132    fn schema_name() -> String {
133        "FontFeatures".into()
134    }
135
136    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
137        let mut schema = SchemaObject::default();
138        schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new(
139            InstanceType::Object,
140        )));
141        {
142            let mut property = SchemaObject {
143                instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![
144                    InstanceType::Boolean,
145                    InstanceType::Integer,
146                ])),
147                ..Default::default()
148            };
149
150            {
151                let mut number_constraints = property.number();
152                number_constraints.multiple_of = Some(1.0);
153                number_constraints.minimum = Some(0.0);
154            }
155            schema
156                .object()
157                .pattern_properties
158                .insert("[0-9a-zA-Z]{4}$".into(), property.into());
159        }
160        schema.into()
161    }
162}
163
164fn is_valid_feature_tag(tag: &str) -> bool {
165    tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())
166}