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>); 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 { 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(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 !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(&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 { 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()) }