fonts.rs

  1use crate::{
  2    color::Color,
  3    font_cache::FamilyId,
  4    json::{json, ToJson},
  5    text_layout::RunStyle,
  6    FontCache,
  7};
  8use anyhow::anyhow;
  9pub use font_kit::{
 10    metrics::Metrics,
 11    properties::{Properties, Stretch, Style, Weight},
 12};
 13use serde::{de, Deserialize};
 14use serde_json::Value;
 15use std::{cell::RefCell, sync::Arc};
 16
 17#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 18pub struct FontId(pub usize);
 19
 20pub type GlyphId = u32;
 21
 22#[derive(Clone, Debug)]
 23pub struct TextStyle {
 24    pub color: Color,
 25    pub font_family_name: Arc<str>,
 26    pub font_family_id: FamilyId,
 27    pub font_id: FontId,
 28    pub font_size: f32,
 29    pub font_properties: Properties,
 30    pub underline: Option<Color>,
 31}
 32
 33#[derive(Copy, Clone, Debug, Default)]
 34pub struct HighlightStyle {
 35    pub color: Color,
 36    pub font_properties: Properties,
 37    pub underline: Option<Color>,
 38}
 39
 40#[allow(non_camel_case_types)]
 41#[derive(Deserialize)]
 42enum WeightJson {
 43    thin,
 44    extra_light,
 45    light,
 46    normal,
 47    medium,
 48    semibold,
 49    bold,
 50    extra_bold,
 51    black,
 52}
 53
 54thread_local! {
 55    static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
 56}
 57
 58#[derive(Deserialize)]
 59struct TextStyleJson {
 60    color: Color,
 61    family: String,
 62    weight: Option<WeightJson>,
 63    size: f32,
 64    #[serde(default)]
 65    italic: bool,
 66    #[serde(default)]
 67    underline: UnderlineStyleJson,
 68}
 69
 70#[derive(Deserialize)]
 71struct HighlightStyleJson {
 72    color: Color,
 73    weight: Option<WeightJson>,
 74    #[serde(default)]
 75    italic: bool,
 76    #[serde(default)]
 77    underline: UnderlineStyleJson,
 78}
 79
 80#[derive(Deserialize)]
 81#[serde(untagged)]
 82enum UnderlineStyleJson {
 83    Underlined(bool),
 84    UnderlinedWithColor(Color),
 85}
 86
 87impl TextStyle {
 88    pub fn new(
 89        font_family_name: impl Into<Arc<str>>,
 90        font_size: f32,
 91        font_properties: Properties,
 92        underline: Option<Color>,
 93        color: Color,
 94        font_cache: &FontCache,
 95    ) -> anyhow::Result<Self> {
 96        let font_family_name = font_family_name.into();
 97        let font_family_id = font_cache.load_family(&[&font_family_name])?;
 98        let font_id = font_cache.select_font(font_family_id, &font_properties)?;
 99        Ok(Self {
100            color,
101            font_family_name,
102            font_family_id,
103            font_id,
104            font_size,
105            font_properties,
106            underline,
107        })
108    }
109
110    pub fn to_run(&self) -> RunStyle {
111        RunStyle {
112            font_id: self.font_id,
113            color: self.color,
114            underline: self.underline,
115        }
116    }
117
118    fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
119        FONT_CACHE.with(|font_cache| {
120            if let Some(font_cache) = font_cache.borrow().as_ref() {
121                let font_properties = properties_from_json(json.weight, json.italic);
122                Self::new(
123                    json.family,
124                    json.size,
125                    font_properties,
126                    underline_from_json(json.underline, json.color),
127                    json.color,
128                    font_cache,
129                )
130            } else {
131                Err(anyhow!(
132                    "TextStyle can only be deserialized within a call to with_font_cache"
133                ))
134            }
135        })
136    }
137
138    pub fn line_height(&self, font_cache: &FontCache) -> f32 {
139        font_cache.line_height(self.font_id, self.font_size)
140    }
141
142    pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
143        font_cache.cap_height(self.font_id, self.font_size)
144    }
145
146    pub fn x_height(&self, font_cache: &FontCache) -> f32 {
147        font_cache.x_height(self.font_id, self.font_size)
148    }
149
150    pub fn em_width(&self, font_cache: &FontCache) -> f32 {
151        font_cache.em_width(self.font_id, self.font_size)
152    }
153
154    pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
155        font_cache.em_advance(self.font_id, self.font_size)
156    }
157
158    pub fn descent(&self, font_cache: &FontCache) -> f32 {
159        font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
160    }
161
162    pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
163        font_cache.baseline_offset(self.font_id, self.font_size)
164    }
165
166    fn em_scale(&self, font_cache: &FontCache) -> f32 {
167        font_cache.em_scale(self.font_id, self.font_size)
168    }
169}
170
171impl From<TextStyle> for HighlightStyle {
172    fn from(other: TextStyle) -> Self {
173        Self {
174            color: other.color,
175            font_properties: other.font_properties,
176            underline: other.underline,
177        }
178    }
179}
180
181impl Default for UnderlineStyleJson {
182    fn default() -> Self {
183        Self::Underlined(false)
184    }
185}
186
187impl Default for TextStyle {
188    fn default() -> Self {
189        FONT_CACHE.with(|font_cache| {
190            let font_cache = font_cache.borrow();
191            let font_cache = font_cache
192                .as_ref()
193                .expect("TextStyle::default can only be called within a call to with_font_cache");
194
195            let font_family_name = Arc::from("Courier");
196            let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
197            let font_id = font_cache
198                .select_font(font_family_id, &Default::default())
199                .unwrap();
200            Self {
201                color: Default::default(),
202                font_family_name,
203                font_family_id,
204                font_id,
205                font_size: 14.,
206                font_properties: Default::default(),
207                underline: Default::default(),
208            }
209        })
210    }
211}
212
213impl HighlightStyle {
214    fn from_json(json: HighlightStyleJson) -> Self {
215        let font_properties = properties_from_json(json.weight, json.italic);
216        Self {
217            color: json.color,
218            font_properties,
219            underline: underline_from_json(json.underline, json.color),
220        }
221    }
222}
223
224impl From<Color> for HighlightStyle {
225    fn from(color: Color) -> Self {
226        Self {
227            color,
228            font_properties: Default::default(),
229            underline: None,
230        }
231    }
232}
233
234impl<'de> Deserialize<'de> for TextStyle {
235    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236    where
237        D: serde::Deserializer<'de>,
238    {
239        Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
240            .map_err(|e| de::Error::custom(e))?)
241    }
242}
243
244impl ToJson for TextStyle {
245    fn to_json(&self) -> Value {
246        json!({
247            "color": self.color.to_json(),
248            "font_family": self.font_family_name.as_ref(),
249            "font_properties": self.font_properties.to_json(),
250        })
251    }
252}
253
254impl<'de> Deserialize<'de> for HighlightStyle {
255    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256    where
257        D: serde::Deserializer<'de>,
258    {
259        let json = serde_json::Value::deserialize(deserializer)?;
260        if json.is_object() {
261            Ok(Self::from_json(
262                serde_json::from_value(json).map_err(de::Error::custom)?,
263            ))
264        } else {
265            Ok(Self {
266                color: serde_json::from_value(json).map_err(de::Error::custom)?,
267                font_properties: Properties::new(),
268                underline: None,
269            })
270        }
271    }
272}
273
274fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Color> {
275    match json {
276        UnderlineStyleJson::Underlined(false) => None,
277        UnderlineStyleJson::Underlined(true) => Some(text_color),
278        UnderlineStyleJson::UnderlinedWithColor(color) => Some(color),
279    }
280}
281
282fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
283    let weight = match weight.unwrap_or(WeightJson::normal) {
284        WeightJson::thin => Weight::THIN,
285        WeightJson::extra_light => Weight::EXTRA_LIGHT,
286        WeightJson::light => Weight::LIGHT,
287        WeightJson::normal => Weight::NORMAL,
288        WeightJson::medium => Weight::MEDIUM,
289        WeightJson::semibold => Weight::SEMIBOLD,
290        WeightJson::bold => Weight::BOLD,
291        WeightJson::extra_bold => Weight::EXTRA_BOLD,
292        WeightJson::black => Weight::BLACK,
293    };
294    let style = if italic { Style::Italic } else { Style::Normal };
295    *Properties::new().weight(weight).style(style)
296}
297
298impl ToJson for Properties {
299    fn to_json(&self) -> crate::json::Value {
300        json!({
301            "style": self.style.to_json(),
302            "weight": self.weight.to_json(),
303            "stretch": self.stretch.to_json(),
304        })
305    }
306}
307
308impl ToJson for Style {
309    fn to_json(&self) -> crate::json::Value {
310        match self {
311            Style::Normal => json!("normal"),
312            Style::Italic => json!("italic"),
313            Style::Oblique => json!("oblique"),
314        }
315    }
316}
317
318impl ToJson for Weight {
319    fn to_json(&self) -> crate::json::Value {
320        if self.0 == Weight::THIN.0 {
321            json!("thin")
322        } else if self.0 == Weight::EXTRA_LIGHT.0 {
323            json!("extra light")
324        } else if self.0 == Weight::LIGHT.0 {
325            json!("light")
326        } else if self.0 == Weight::NORMAL.0 {
327            json!("normal")
328        } else if self.0 == Weight::MEDIUM.0 {
329            json!("medium")
330        } else if self.0 == Weight::SEMIBOLD.0 {
331            json!("semibold")
332        } else if self.0 == Weight::BOLD.0 {
333            json!("bold")
334        } else if self.0 == Weight::EXTRA_BOLD.0 {
335            json!("extra bold")
336        } else if self.0 == Weight::BLACK.0 {
337            json!("black")
338        } else {
339            json!(self.0)
340        }
341    }
342}
343
344impl ToJson for Stretch {
345    fn to_json(&self) -> serde_json::Value {
346        json!(self.0)
347    }
348}
349
350pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
351where
352    F: FnOnce() -> T,
353{
354    FONT_CACHE.with(|cache| {
355        *cache.borrow_mut() = Some(font_cache);
356        let result = callback();
357        cache.borrow_mut().take();
358        result
359    })
360}