term_model/config/
font.rs

1use std::fmt;
2
3#[cfg(not(target_os = "fuchsia"))]
4use font::Size;
5use log::error;
6use serde::de::Visitor;
7use serde::{Deserialize, Deserializer};
8
9#[cfg(target_os = "macos")]
10use crate::config::DefaultTrueBool;
11use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG};
12
13#[cfg(target_os = "fuchsia")]
14#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
15pub struct Size(i16);
16
17#[cfg(target_os = "fuchsia")]
18impl Size {
19    /// Create a new `Size` from a f32 size in points
20    pub fn new(size: f32) -> Size {
21        Size((size * Size::factor()) as i16)
22    }
23
24    /// Scale factor between font "Size" type and point size
25    #[inline]
26    pub fn factor() -> f32 {
27        2.0
28    }
29
30    /// Get the f32 size in points
31    pub fn as_f32_pts(self) -> f32 {
32        f32::from(self.0) / Size::factor()
33    }
34}
35
36/// Font config
37///
38/// Defaults are provided at the level of this struct per platform, but not per
39/// field in this struct. It might be nice in the future to have defaults for
40/// each value independently. Alternatively, maybe erroring when the user
41/// doesn't provide complete config is Ok.
42#[serde(default)]
43#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
44pub struct Font {
45    /// Normal font face
46    #[serde(deserialize_with = "failure_default")]
47    normal: FontDescription,
48
49    /// Bold font face
50    #[serde(deserialize_with = "failure_default")]
51    bold: SecondaryFontDescription,
52
53    /// Italic font face
54    #[serde(deserialize_with = "failure_default")]
55    italic: SecondaryFontDescription,
56
57    /// Bold italic font face
58    #[serde(deserialize_with = "failure_default")]
59    bold_italic: SecondaryFontDescription,
60
61    /// Font size in points
62    #[serde(deserialize_with = "DeserializeSize::deserialize")]
63    pub size: Size,
64
65    /// Extra spacing per character
66    #[serde(deserialize_with = "failure_default")]
67    pub offset: Delta<i8>,
68
69    /// Glyph offset within character cell
70    #[serde(deserialize_with = "failure_default")]
71    pub glyph_offset: Delta<i8>,
72
73    #[cfg(target_os = "macos")]
74    #[serde(deserialize_with = "failure_default")]
75    use_thin_strokes: DefaultTrueBool,
76}
77
78impl Default for Font {
79    fn default() -> Font {
80        Font {
81            size: default_font_size(),
82            normal: Default::default(),
83            bold: Default::default(),
84            italic: Default::default(),
85            bold_italic: Default::default(),
86            glyph_offset: Default::default(),
87            offset: Default::default(),
88            #[cfg(target_os = "macos")]
89            use_thin_strokes: Default::default(),
90        }
91    }
92}
93
94impl Font {
95    /// Get a font clone with a size modification
96    pub fn with_size(self, size: Size) -> Font {
97        Font { size, ..self }
98    }
99
100    // Get normal font description
101    pub fn normal(&self) -> &FontDescription {
102        &self.normal
103    }
104
105    // Get bold font description
106    pub fn bold(&self) -> FontDescription {
107        self.bold.desc(&self.normal)
108    }
109
110    // Get italic font description
111    pub fn italic(&self) -> FontDescription {
112        self.italic.desc(&self.normal)
113    }
114
115    // Get bold italic font description
116    pub fn bold_italic(&self) -> FontDescription {
117        self.bold_italic.desc(&self.normal)
118    }
119
120    #[cfg(target_os = "macos")]
121    pub fn use_thin_strokes(&self) -> bool {
122        self.use_thin_strokes.0
123    }
124
125    #[cfg(not(target_os = "macos"))]
126    pub fn use_thin_strokes(&self) -> bool {
127        false
128    }
129}
130
131fn default_font_size() -> Size {
132    Size::new(11.)
133}
134
135/// Description of the normal font
136#[serde(default)]
137#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
138pub struct FontDescription {
139    #[serde(deserialize_with = "failure_default")]
140    pub family: String,
141    #[serde(deserialize_with = "failure_default")]
142    pub style: Option<String>,
143}
144
145impl Default for FontDescription {
146    fn default() -> FontDescription {
147        FontDescription {
148            #[cfg(not(any(target_os = "macos", windows)))]
149            family: "monospace".into(),
150            #[cfg(target_os = "macos")]
151            family: "Menlo".into(),
152            #[cfg(windows)]
153            family: "Consolas".into(),
154            style: None,
155        }
156    }
157}
158
159/// Description of the italic and bold font
160#[serde(default)]
161#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
162pub struct SecondaryFontDescription {
163    #[serde(deserialize_with = "failure_default")]
164    family: Option<String>,
165    #[serde(deserialize_with = "failure_default")]
166    style: Option<String>,
167}
168
169impl SecondaryFontDescription {
170    pub fn desc(&self, fallback: &FontDescription) -> FontDescription {
171        FontDescription {
172            family: self.family.clone().unwrap_or_else(|| fallback.family.clone()),
173            style: self.style.clone(),
174        }
175    }
176}
177
178trait DeserializeSize: Sized {
179    fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error>
180    where
181        D: serde::de::Deserializer<'a>;
182}
183
184impl DeserializeSize for Size {
185    fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
186    where
187        D: serde::de::Deserializer<'a>,
188    {
189        use std::marker::PhantomData;
190
191        struct NumVisitor<__D> {
192            _marker: PhantomData<__D>,
193        }
194
195        impl<'a, __D> Visitor<'a> for NumVisitor<__D>
196        where
197            __D: serde::de::Deserializer<'a>,
198        {
199            type Value = f64;
200
201            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202                f.write_str("f64 or u64")
203            }
204
205            fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E>
206            where
207                E: ::serde::de::Error,
208            {
209                Ok(value)
210            }
211
212            fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E>
213            where
214                E: ::serde::de::Error,
215            {
216                Ok(value as f64)
217            }
218        }
219
220        #[cfg(not(target_os = "fuchsia"))]
221        let value = serde_yaml::Value::deserialize(deserializer)?;
222        #[cfg(target_os = "fuchsia")]
223        let value = serde_json::Value::deserialize(deserializer)?;
224        let size = value
225            .deserialize_any(NumVisitor::<D> { _marker: PhantomData })
226            .map(|v| Size::new(v as _));
227
228        // Use default font size as fallback
229        match size {
230            Ok(size) => Ok(size),
231            Err(err) => {
232                let size = default_font_size();
233                error!(
234                    target: LOG_TARGET_CONFIG,
235                    "Problem with config: {}; using size {}",
236                    err,
237                    size.as_f32_pts()
238                );
239                Ok(size)
240            },
241        }
242    }
243}