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