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