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 From<TextStyle> for HighlightStyle {
130    fn from(other: TextStyle) -> Self {
131        Self {
132            color: other.color,
133            font_properties: other.font_properties,
134            underline: other.underline,
135        }
136    }
137}
138
139impl HighlightStyle {
140    fn from_json(json: HighlightStyleJson) -> Self {
141        let font_properties = properties_from_json(json.weight, json.italic);
142        Self {
143            color: json.color,
144            font_properties,
145            underline: json.underline,
146        }
147    }
148}
149
150impl From<Color> for HighlightStyle {
151    fn from(color: Color) -> Self {
152        Self {
153            color,
154            font_properties: Default::default(),
155            underline: false,
156        }
157    }
158}
159
160impl<'de> Deserialize<'de> for TextStyle {
161    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162    where
163        D: serde::Deserializer<'de>,
164    {
165        Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
166            .map_err(|e| de::Error::custom(e))?)
167    }
168}
169
170impl ToJson for TextStyle {
171    fn to_json(&self) -> Value {
172        json!({
173            "color": self.color.to_json(),
174            "font_family": self.font_family_name.as_ref(),
175            "font_properties": self.font_properties.to_json(),
176        })
177    }
178}
179
180impl<'de> Deserialize<'de> for HighlightStyle {
181    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182    where
183        D: serde::Deserializer<'de>,
184    {
185        let json = serde_json::Value::deserialize(deserializer)?;
186        if json.is_object() {
187            Ok(Self::from_json(
188                serde_json::from_value(json).map_err(de::Error::custom)?,
189            ))
190        } else {
191            Ok(Self {
192                color: serde_json::from_value(json).map_err(de::Error::custom)?,
193                font_properties: Properties::new(),
194                underline: false,
195            })
196        }
197    }
198}
199
200fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
201    let weight = match weight.unwrap_or(WeightJson::normal) {
202        WeightJson::thin => Weight::THIN,
203        WeightJson::extra_light => Weight::EXTRA_LIGHT,
204        WeightJson::light => Weight::LIGHT,
205        WeightJson::normal => Weight::NORMAL,
206        WeightJson::medium => Weight::MEDIUM,
207        WeightJson::semibold => Weight::SEMIBOLD,
208        WeightJson::bold => Weight::BOLD,
209        WeightJson::extra_bold => Weight::EXTRA_BOLD,
210        WeightJson::black => Weight::BLACK,
211    };
212    let style = if italic { Style::Italic } else { Style::Normal };
213    *Properties::new().weight(weight).style(style)
214}
215
216impl ToJson for Properties {
217    fn to_json(&self) -> crate::json::Value {
218        json!({
219            "style": self.style.to_json(),
220            "weight": self.weight.to_json(),
221            "stretch": self.stretch.to_json(),
222        })
223    }
224}
225
226impl ToJson for Style {
227    fn to_json(&self) -> crate::json::Value {
228        match self {
229            Style::Normal => json!("normal"),
230            Style::Italic => json!("italic"),
231            Style::Oblique => json!("oblique"),
232        }
233    }
234}
235
236impl ToJson for Weight {
237    fn to_json(&self) -> crate::json::Value {
238        if self.0 == Weight::THIN.0 {
239            json!("thin")
240        } else if self.0 == Weight::EXTRA_LIGHT.0 {
241            json!("extra light")
242        } else if self.0 == Weight::LIGHT.0 {
243            json!("light")
244        } else if self.0 == Weight::NORMAL.0 {
245            json!("normal")
246        } else if self.0 == Weight::MEDIUM.0 {
247            json!("medium")
248        } else if self.0 == Weight::SEMIBOLD.0 {
249            json!("semibold")
250        } else if self.0 == Weight::BOLD.0 {
251            json!("bold")
252        } else if self.0 == Weight::EXTRA_BOLD.0 {
253            json!("extra bold")
254        } else if self.0 == Weight::BLACK.0 {
255            json!("black")
256        } else {
257            json!(self.0)
258        }
259    }
260}
261
262impl ToJson for Stretch {
263    fn to_json(&self) -> serde_json::Value {
264        json!(self.0)
265    }
266}
267
268pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
269where
270    F: FnOnce() -> T,
271{
272    FONT_CACHE.with(|cache| {
273        *cache.borrow_mut() = Some(font_cache);
274        let result = callback();
275        cache.borrow_mut().take();
276        result
277    })
278}