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}