fonts.rs

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