fonts.rs

  1use crate::{
  2    color::Color,
  3    json::{json, ToJson},
  4    FontCache,
  5};
  6use anyhow::anyhow;
  7pub use font_kit::{
  8    metrics::Metrics,
  9    properties::{Properties, Stretch, Style, Weight},
 10};
 11use serde::{de, Deserialize};
 12use serde_json::Value;
 13use std::{cell::RefCell, sync::Arc};
 14
 15#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 16pub struct FontId(pub usize);
 17
 18pub type GlyphId = u32;
 19
 20#[derive(Clone, Debug)]
 21pub struct TextStyle {
 22    pub color: Color,
 23    pub font_family_name: Arc<str>,
 24    pub font_id: FontId,
 25    pub font_size: f32,
 26    pub font_properties: Properties,
 27}
 28
 29#[derive(Clone, Debug, Default)]
 30pub struct HighlightStyle {
 31    pub color: Color,
 32    pub font_properties: Properties,
 33}
 34
 35#[allow(non_camel_case_types)]
 36#[derive(Deserialize)]
 37enum WeightJson {
 38    thin,
 39    extra_light,
 40    light,
 41    normal,
 42    medium,
 43    semibold,
 44    bold,
 45    extra_bold,
 46    black,
 47}
 48
 49thread_local! {
 50    static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
 51}
 52
 53#[derive(Deserialize)]
 54struct TextStyleJson {
 55    color: Color,
 56    family: String,
 57    weight: Option<WeightJson>,
 58    #[serde(default)]
 59    italic: bool,
 60    size: f32,
 61}
 62
 63#[derive(Deserialize)]
 64struct HighlightStyleJson {
 65    color: Color,
 66    weight: Option<WeightJson>,
 67    #[serde(default)]
 68    italic: bool,
 69}
 70
 71impl TextStyle {
 72    pub fn new(
 73        font_family_name: impl Into<Arc<str>>,
 74        font_size: f32,
 75        font_properties: Properties,
 76        color: Color,
 77        font_cache: &FontCache,
 78    ) -> anyhow::Result<Self> {
 79        let font_family_name = font_family_name.into();
 80        let family_id = font_cache.load_family(&[&font_family_name])?;
 81        let font_id = font_cache.select_font(family_id, &font_properties)?;
 82        Ok(Self {
 83            color,
 84            font_family_name,
 85            font_id,
 86            font_size,
 87            font_properties,
 88        })
 89    }
 90
 91    fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
 92        FONT_CACHE.with(|font_cache| {
 93            if let Some(font_cache) = font_cache.borrow().as_ref() {
 94                let font_properties = properties_from_json(json.weight, json.italic);
 95                Self::new(
 96                    json.family,
 97                    json.size,
 98                    font_properties,
 99                    json.color,
100                    font_cache,
101                )
102            } else {
103                Err(anyhow!(
104                    "TextStyle can only be deserialized within a call to with_font_cache"
105                ))
106            }
107        })
108    }
109}
110
111impl HighlightStyle {
112    fn from_json(json: HighlightStyleJson) -> Self {
113        let font_properties = properties_from_json(json.weight, json.italic);
114        Self {
115            color: json.color,
116            font_properties,
117        }
118    }
119}
120
121impl From<Color> for HighlightStyle {
122    fn from(color: Color) -> Self {
123        Self {
124            color,
125            font_properties: Default::default(),
126        }
127    }
128}
129
130impl<'de> Deserialize<'de> for TextStyle {
131    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de>,
134    {
135        Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
136            .map_err(|e| de::Error::custom(e))?)
137    }
138}
139
140impl ToJson for TextStyle {
141    fn to_json(&self) -> Value {
142        json!({
143            "color": self.color.to_json(),
144            "font_family": self.font_family_name.as_ref(),
145            "font_properties": self.font_properties.to_json(),
146        })
147    }
148}
149
150impl<'de> Deserialize<'de> for HighlightStyle {
151    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152    where
153        D: serde::Deserializer<'de>,
154    {
155        let json = serde_json::Value::deserialize(deserializer)?;
156        if json.is_object() {
157            Ok(Self::from_json(
158                serde_json::from_value(json).map_err(de::Error::custom)?,
159            ))
160        } else {
161            Ok(Self {
162                color: serde_json::from_value(json).map_err(de::Error::custom)?,
163                font_properties: Properties::new(),
164            })
165        }
166    }
167}
168
169fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
170    let weight = match weight.unwrap_or(WeightJson::normal) {
171        WeightJson::thin => Weight::THIN,
172        WeightJson::extra_light => Weight::EXTRA_LIGHT,
173        WeightJson::light => Weight::LIGHT,
174        WeightJson::normal => Weight::NORMAL,
175        WeightJson::medium => Weight::MEDIUM,
176        WeightJson::semibold => Weight::SEMIBOLD,
177        WeightJson::bold => Weight::BOLD,
178        WeightJson::extra_bold => Weight::EXTRA_BOLD,
179        WeightJson::black => Weight::BLACK,
180    };
181    let style = if italic { Style::Italic } else { Style::Normal };
182    *Properties::new().weight(weight).style(style)
183}
184
185impl ToJson for Properties {
186    fn to_json(&self) -> crate::json::Value {
187        json!({
188            "style": self.style.to_json(),
189            "weight": self.weight.to_json(),
190            "stretch": self.stretch.to_json(),
191        })
192    }
193}
194
195impl ToJson for Style {
196    fn to_json(&self) -> crate::json::Value {
197        match self {
198            Style::Normal => json!("normal"),
199            Style::Italic => json!("italic"),
200            Style::Oblique => json!("oblique"),
201        }
202    }
203}
204
205impl ToJson for Weight {
206    fn to_json(&self) -> crate::json::Value {
207        if self.0 == Weight::THIN.0 {
208            json!("thin")
209        } else if self.0 == Weight::EXTRA_LIGHT.0 {
210            json!("extra light")
211        } else if self.0 == Weight::LIGHT.0 {
212            json!("light")
213        } else if self.0 == Weight::NORMAL.0 {
214            json!("normal")
215        } else if self.0 == Weight::MEDIUM.0 {
216            json!("medium")
217        } else if self.0 == Weight::SEMIBOLD.0 {
218            json!("semibold")
219        } else if self.0 == Weight::BOLD.0 {
220            json!("bold")
221        } else if self.0 == Weight::EXTRA_BOLD.0 {
222            json!("extra bold")
223        } else if self.0 == Weight::BLACK.0 {
224            json!("black")
225        } else {
226            json!(self.0)
227        }
228    }
229}
230
231impl ToJson for Stretch {
232    fn to_json(&self) -> serde_json::Value {
233        json!(self.0)
234    }
235}
236
237pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
238where
239    F: FnOnce() -> T,
240{
241    FONT_CACHE.with(|cache| {
242        *cache.borrow_mut() = Some(font_cache);
243        let result = callback();
244        cache.borrow_mut().take();
245        result
246    })
247}