166 lines · 5.2 KB · Rust
RawDownload
use std::sync::Arc; 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(pub Arc<Vec<(String, u32)>>); impl FontFeatures {    /// Disables `calt`.    pub fn disable_ligatures() -> Self {        Self(Arc::new(vec![("calt".into(), 0)]))    }     /// 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()    }     /// Returns whether the `calt` feature is enabled.    ///    /// Returns `None` if the feature is not present.    pub fn is_calt_enabled(&self) -> Option<bool> {        self.0            .iter()            .find(|(feature, _)| feature == "calt")            .map(|(_, value)| *value == 1)    }} 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<D>(deserializer: D) -> Result<Self, D::Error>    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<M>(self, mut access: M) -> Result<Self::Value, M::Error>            where                M: MapAccess<'de>,            {                let mut feature_list = Vec::new();                 while let Some((key, value)) =                    access.next_entry::<String, Option<FeatureValue>>()?                {                    if !is_valid_feature_tag(&key) {                        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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    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 {                instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![                    InstanceType::Boolean,                    InstanceType::Integer,                ])),                ..Default::default()            };             {                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()    }} fn is_valid_feature_tag(tag: &str) -> bool {    tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())}