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: bool,
 31}
 32
 33#[derive(Clone, Debug, Default)]
 34pub struct HighlightStyle {
 35    pub color: Color,
 36    pub font_properties: Properties,
 37    pub underline: bool,
 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: bool,
 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: bool,
 78}
 79
 80impl TextStyle {
 81    pub fn new(
 82        font_family_name: impl Into<Arc<str>>,
 83        font_size: f32,
 84        font_properties: Properties,
 85        underline: bool,
 86        color: Color,
 87        font_cache: &FontCache,
 88    ) -> anyhow::Result<Self> {
 89        let font_family_name = font_family_name.into();
 90        let font_family_id = font_cache.load_family(&[&font_family_name])?;
 91        let font_id = font_cache.select_font(font_family_id, &font_properties)?;
 92        Ok(Self {
 93            color,
 94            font_family_name,
 95            font_family_id,
 96            font_id,
 97            font_size,
 98            font_properties,
 99            underline,
100        })
101    }
102
103    pub fn to_run(&self) -> RunStyle {
104        RunStyle {
105            font_id: self.font_id,
106            color: self.color,
107            underline: self.underline,
108        }
109    }
110
111    fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
112        FONT_CACHE.with(|font_cache| {
113            if let Some(font_cache) = font_cache.borrow().as_ref() {
114                let font_properties = properties_from_json(json.weight, json.italic);
115                Self::new(
116                    json.family,
117                    json.size,
118                    font_properties,
119                    json.underline,
120                    json.color,
121                    font_cache,
122                )
123            } else {
124                Err(anyhow!(
125                    "TextStyle can only be deserialized within a call to with_font_cache"
126                ))
127            }
128        })
129    }
130
131    pub fn line_height(&self, font_cache: &FontCache) -> f32 {
132        font_cache.line_height(self.font_id, self.font_size)
133    }
134
135    pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
136        font_cache.cap_height(self.font_id, self.font_size)
137    }
138
139    pub fn x_height(&self, font_cache: &FontCache) -> f32 {
140        font_cache.x_height(self.font_id, self.font_size)
141    }
142
143    pub fn em_width(&self, font_cache: &FontCache) -> f32 {
144        font_cache.em_width(self.font_id, self.font_size)
145    }
146
147    pub fn descent(&self, font_cache: &FontCache) -> f32 {
148        font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
149    }
150
151    pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
152        font_cache.baseline_offset(self.font_id, self.font_size)
153    }
154
155    fn em_scale(&self, font_cache: &FontCache) -> f32 {
156        font_cache.em_scale(self.font_id, self.font_size)
157    }
158}
159
160impl From<TextStyle> for HighlightStyle {
161    fn from(other: TextStyle) -> Self {
162        Self {
163            color: other.color,
164            font_properties: other.font_properties,
165            underline: other.underline,
166        }
167    }
168}
169
170impl Default for TextStyle {
171    fn default() -> Self {
172        FONT_CACHE.with(|font_cache| {
173            let font_cache = font_cache.borrow();
174            let font_cache = font_cache
175                .as_ref()
176                .expect("TextStyle::default can only be called within a call to with_font_cache");
177
178            let font_family_name = Arc::from("Courier");
179            let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
180            let font_id = font_cache
181                .select_font(font_family_id, &Default::default())
182                .unwrap();
183            Self {
184                color: Default::default(),
185                font_family_name,
186                font_family_id,
187                font_id,
188                font_size: 14.,
189                font_properties: Default::default(),
190                underline: Default::default(),
191            }
192        })
193    }
194}
195
196impl HighlightStyle {
197    fn from_json(json: HighlightStyleJson) -> Self {
198        let font_properties = properties_from_json(json.weight, json.italic);
199        Self {
200            color: json.color,
201            font_properties,
202            underline: json.underline,
203        }
204    }
205}
206
207impl From<Color> for HighlightStyle {
208    fn from(color: Color) -> Self {
209        Self {
210            color,
211            font_properties: Default::default(),
212            underline: false,
213        }
214    }
215}
216
217impl<'de> Deserialize<'de> for TextStyle {
218    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219    where
220        D: serde::Deserializer<'de>,
221    {
222        Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
223            .map_err(|e| de::Error::custom(e))?)
224    }
225}
226
227impl ToJson for TextStyle {
228    fn to_json(&self) -> Value {
229        json!({
230            "color": self.color.to_json(),
231            "font_family": self.font_family_name.as_ref(),
232            "font_properties": self.font_properties.to_json(),
233        })
234    }
235}
236
237impl<'de> Deserialize<'de> for HighlightStyle {
238    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239    where
240        D: serde::Deserializer<'de>,
241    {
242        let json = serde_json::Value::deserialize(deserializer)?;
243        if json.is_object() {
244            Ok(Self::from_json(
245                serde_json::from_value(json).map_err(de::Error::custom)?,
246            ))
247        } else {
248            Ok(Self {
249                color: serde_json::from_value(json).map_err(de::Error::custom)?,
250                font_properties: Properties::new(),
251                underline: false,
252            })
253        }
254    }
255}
256
257fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
258    let weight = match weight.unwrap_or(WeightJson::normal) {
259        WeightJson::thin => Weight::THIN,
260        WeightJson::extra_light => Weight::EXTRA_LIGHT,
261        WeightJson::light => Weight::LIGHT,
262        WeightJson::normal => Weight::NORMAL,
263        WeightJson::medium => Weight::MEDIUM,
264        WeightJson::semibold => Weight::SEMIBOLD,
265        WeightJson::bold => Weight::BOLD,
266        WeightJson::extra_bold => Weight::EXTRA_BOLD,
267        WeightJson::black => Weight::BLACK,
268    };
269    let style = if italic { Style::Italic } else { Style::Normal };
270    *Properties::new().weight(weight).style(style)
271}
272
273impl ToJson for Properties {
274    fn to_json(&self) -> crate::json::Value {
275        json!({
276            "style": self.style.to_json(),
277            "weight": self.weight.to_json(),
278            "stretch": self.stretch.to_json(),
279        })
280    }
281}
282
283impl ToJson for Style {
284    fn to_json(&self) -> crate::json::Value {
285        match self {
286            Style::Normal => json!("normal"),
287            Style::Italic => json!("italic"),
288            Style::Oblique => json!("oblique"),
289        }
290    }
291}
292
293impl ToJson for Weight {
294    fn to_json(&self) -> crate::json::Value {
295        if self.0 == Weight::THIN.0 {
296            json!("thin")
297        } else if self.0 == Weight::EXTRA_LIGHT.0 {
298            json!("extra light")
299        } else if self.0 == Weight::LIGHT.0 {
300            json!("light")
301        } else if self.0 == Weight::NORMAL.0 {
302            json!("normal")
303        } else if self.0 == Weight::MEDIUM.0 {
304            json!("medium")
305        } else if self.0 == Weight::SEMIBOLD.0 {
306            json!("semibold")
307        } else if self.0 == Weight::BOLD.0 {
308            json!("bold")
309        } else if self.0 == Weight::EXTRA_BOLD.0 {
310            json!("extra bold")
311        } else if self.0 == Weight::BLACK.0 {
312            json!("black")
313        } else {
314            json!(self.0)
315        }
316    }
317}
318
319impl ToJson for Stretch {
320    fn to_json(&self) -> serde_json::Value {
321        json!(self.0)
322    }
323}
324
325pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
326where
327    F: FnOnce() -> T,
328{
329    FONT_CACHE.with(|cache| {
330        *cache.borrow_mut() = Some(font_cache);
331        let result = callback();
332        cache.borrow_mut().take();
333        result
334    })
335}