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