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 schemars::JsonSchema;
 15use serde::{de, Deserialize, Serialize};
 16use serde_json::Value;
 17use std::{cell::RefCell, sync::Arc};
 18
 19#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
 20pub struct FontId(pub usize);
 21
 22pub type GlyphId = u32;
 23
 24#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
 25pub struct Features {
 26    pub calt: Option<bool>,
 27    pub case: Option<bool>,
 28    pub cpsp: Option<bool>,
 29    pub frac: Option<bool>,
 30    pub liga: Option<bool>,
 31    pub onum: Option<bool>,
 32    pub ordn: Option<bool>,
 33    pub pnum: Option<bool>,
 34    pub ss01: Option<bool>,
 35    pub ss02: Option<bool>,
 36    pub ss03: Option<bool>,
 37    pub ss04: Option<bool>,
 38    pub ss05: Option<bool>,
 39    pub ss06: Option<bool>,
 40    pub ss07: Option<bool>,
 41    pub ss08: Option<bool>,
 42    pub ss09: Option<bool>,
 43    pub ss10: Option<bool>,
 44    pub ss11: Option<bool>,
 45    pub ss12: Option<bool>,
 46    pub ss13: Option<bool>,
 47    pub ss14: Option<bool>,
 48    pub ss15: Option<bool>,
 49    pub ss16: Option<bool>,
 50    pub ss17: Option<bool>,
 51    pub ss18: Option<bool>,
 52    pub ss19: Option<bool>,
 53    pub ss20: Option<bool>,
 54    pub subs: Option<bool>,
 55    pub sups: Option<bool>,
 56    pub swsh: Option<bool>,
 57    pub titl: Option<bool>,
 58    pub tnum: Option<bool>,
 59    pub zero: Option<bool>,
 60}
 61
 62#[derive(Clone, Debug, JsonSchema)]
 63pub struct TextStyle {
 64    pub color: Color,
 65    pub font_family_name: Arc<str>,
 66    pub font_family_id: FamilyId,
 67    pub font_id: FontId,
 68    pub font_size: f32,
 69    #[schemars(with = "PropertiesDef")]
 70    pub font_properties: Properties,
 71    pub underline: Underline,
 72}
 73
 74#[derive(JsonSchema)]
 75#[serde(remote = "Properties")]
 76pub struct PropertiesDef {
 77    /// The font style, as defined in CSS.
 78    pub style: StyleDef,
 79    /// The font weight, as defined in CSS.
 80    pub weight: f32,
 81    /// The font stretchiness, as defined in CSS.
 82    pub stretch: f32,
 83}
 84
 85#[derive(JsonSchema)]
 86#[schemars(remote = "Style")]
 87pub enum StyleDef {
 88    /// A face that is neither italic not obliqued.
 89    Normal,
 90    /// A form that is generally cursive in nature.
 91    Italic,
 92    /// A typically-sloped version of the regular face.
 93    Oblique,
 94}
 95
 96#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
 97pub struct HighlightStyle {
 98    pub color: Option<Color>,
 99    #[schemars(with = "Option::<f32>")]
100    pub weight: Option<Weight>,
101    pub italic: Option<bool>,
102    pub underline: Option<Underline>,
103    pub fade_out: Option<f32>,
104}
105
106impl Eq for HighlightStyle {}
107
108#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
109pub struct Underline {
110    pub color: Option<Color>,
111    #[schemars(with = "f32")]
112    pub thickness: OrderedFloat<f32>,
113    pub squiggly: bool,
114}
115
116#[allow(non_camel_case_types)]
117#[derive(Deserialize)]
118enum WeightJson {
119    thin,
120    extra_light,
121    light,
122    normal,
123    medium,
124    semibold,
125    bold,
126    extra_bold,
127    black,
128}
129
130thread_local! {
131    static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
132}
133
134#[derive(Deserialize)]
135struct TextStyleJson {
136    color: Color,
137    family: String,
138    #[serde(default)]
139    features: Features,
140    weight: Option<WeightJson>,
141    size: f32,
142    #[serde(default)]
143    italic: bool,
144    #[serde(default)]
145    underline: UnderlineStyleJson,
146}
147
148#[derive(Deserialize)]
149struct HighlightStyleJson {
150    color: Option<Color>,
151    weight: Option<WeightJson>,
152    italic: Option<bool>,
153    underline: Option<UnderlineStyleJson>,
154    fade_out: Option<f32>,
155}
156
157#[derive(Deserialize)]
158#[serde(untagged)]
159enum UnderlineStyleJson {
160    Underlined(bool),
161    UnderlinedWithProperties {
162        #[serde(default)]
163        color: Option<Color>,
164        #[serde(default)]
165        thickness: Option<f32>,
166        #[serde(default)]
167        squiggly: bool,
168    },
169}
170
171impl TextStyle {
172    pub fn new(
173        font_family_name: impl Into<Arc<str>>,
174        font_size: f32,
175        font_properties: Properties,
176        font_features: Features,
177        underline: Underline,
178        color: Color,
179        font_cache: &FontCache,
180    ) -> Result<Self> {
181        let font_family_name = font_family_name.into();
182        let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
183        let font_id = font_cache.select_font(font_family_id, &font_properties)?;
184        Ok(Self {
185            color,
186            font_family_name,
187            font_family_id,
188            font_id,
189            font_size,
190            font_properties,
191            underline,
192        })
193    }
194
195    pub fn with_font_size(mut self, font_size: f32) -> Self {
196        self.font_size = font_size;
197        self
198    }
199
200    pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
201        let mut font_properties = self.font_properties;
202        if let Some(weight) = style.weight {
203            font_properties.weight(weight);
204        }
205        if let Some(italic) = style.italic {
206            if italic {
207                font_properties.style(Style::Italic);
208            } else {
209                font_properties.style(Style::Normal);
210            }
211        }
212
213        if self.font_properties != font_properties {
214            self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
215        }
216        if let Some(color) = style.color {
217            self.color = Color::blend(color, self.color);
218        }
219        if let Some(factor) = style.fade_out {
220            self.color.fade_out(factor);
221        }
222        if let Some(underline) = style.underline {
223            self.underline = underline;
224        }
225
226        Ok(self)
227    }
228
229    pub fn to_run(&self) -> RunStyle {
230        RunStyle {
231            font_id: self.font_id,
232            color: self.color,
233            underline: self.underline,
234        }
235    }
236
237    fn from_json(json: TextStyleJson) -> Result<Self> {
238        FONT_CACHE.with(|font_cache| {
239            if let Some(font_cache) = font_cache.borrow().as_ref() {
240                let font_properties = properties_from_json(json.weight, json.italic);
241                Self::new(
242                    json.family,
243                    json.size,
244                    font_properties,
245                    json.features,
246                    underline_from_json(json.underline),
247                    json.color,
248                    font_cache,
249                )
250            } else {
251                Err(anyhow!(
252                    "TextStyle can only be deserialized within a call to with_font_cache"
253                ))
254            }
255        })
256    }
257
258    pub fn line_height(&self, font_cache: &FontCache) -> f32 {
259        font_cache.line_height(self.font_size)
260    }
261
262    pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
263        font_cache.cap_height(self.font_id, self.font_size)
264    }
265
266    pub fn x_height(&self, font_cache: &FontCache) -> f32 {
267        font_cache.x_height(self.font_id, self.font_size)
268    }
269
270    pub fn em_width(&self, font_cache: &FontCache) -> f32 {
271        font_cache.em_width(self.font_id, self.font_size)
272    }
273
274    pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
275        font_cache.em_advance(self.font_id, self.font_size)
276    }
277
278    pub fn descent(&self, font_cache: &FontCache) -> f32 {
279        font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
280    }
281
282    pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
283        font_cache.baseline_offset(self.font_id, self.font_size)
284    }
285
286    fn em_scale(&self, font_cache: &FontCache) -> f32 {
287        font_cache.em_scale(self.font_id, self.font_size)
288    }
289}
290
291impl From<TextStyle> for HighlightStyle {
292    fn from(other: TextStyle) -> Self {
293        Self::from(&other)
294    }
295}
296
297impl From<&TextStyle> for HighlightStyle {
298    fn from(other: &TextStyle) -> Self {
299        Self {
300            color: Some(other.color),
301            weight: Some(other.font_properties.weight),
302            italic: Some(other.font_properties.style == Style::Italic),
303            underline: Some(other.underline),
304            fade_out: None,
305        }
306    }
307}
308
309impl Default for UnderlineStyleJson {
310    fn default() -> Self {
311        Self::Underlined(false)
312    }
313}
314
315impl Default for TextStyle {
316    fn default() -> Self {
317        FONT_CACHE.with(|font_cache| {
318            let font_cache = font_cache.borrow();
319            let font_cache = font_cache
320                .as_ref()
321                .expect("TextStyle::default can only be called within a call to with_font_cache");
322
323            let font_family_id = font_cache.known_existing_family();
324            let font_id = font_cache
325                .select_font(font_family_id, &Default::default())
326                .expect("did not have any font in system-provided family");
327            let font_family_name = font_cache
328                .family_name(font_family_id)
329                .expect("we loaded this family from the font cache, so this should work");
330
331            Self {
332                color: Default::default(),
333                font_family_name,
334                font_family_id,
335                font_id,
336                font_size: 14.,
337                font_properties: Default::default(),
338                underline: Default::default(),
339            }
340        })
341    }
342}
343
344impl HighlightStyle {
345    fn from_json(json: HighlightStyleJson) -> Self {
346        Self {
347            color: json.color,
348            weight: json.weight.map(weight_from_json),
349            italic: json.italic,
350            underline: json.underline.map(underline_from_json),
351            fade_out: json.fade_out,
352        }
353    }
354
355    pub fn highlight(&mut self, other: HighlightStyle) {
356        match (self.color, other.color) {
357            (Some(self_color), Some(other_color)) => {
358                self.color = Some(Color::blend(other_color, self_color));
359            }
360            (None, Some(other_color)) => {
361                self.color = Some(other_color);
362            }
363            _ => {}
364        }
365
366        if other.weight.is_some() {
367            self.weight = other.weight;
368        }
369
370        if other.italic.is_some() {
371            self.italic = other.italic;
372        }
373
374        if other.underline.is_some() {
375            self.underline = other.underline;
376        }
377
378        match (other.fade_out, self.fade_out) {
379            (Some(source_fade), None) => self.fade_out = Some(source_fade),
380            (Some(source_fade), Some(dest_fade)) => {
381                let source_alpha = 1. - source_fade;
382                let dest_alpha = 1. - dest_fade;
383                let blended_alpha = source_alpha + (dest_alpha * source_fade);
384                let blended_fade = 1. - blended_alpha;
385                self.fade_out = Some(blended_fade);
386            }
387            _ => {}
388        }
389    }
390}
391
392impl From<Color> for HighlightStyle {
393    fn from(color: Color) -> Self {
394        Self {
395            color: Some(color),
396            ..Default::default()
397        }
398    }
399}
400
401impl<'de> Deserialize<'de> for TextStyle {
402    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
403    where
404        D: serde::Deserializer<'de>,
405    {
406        Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
407    }
408}
409
410impl ToJson for TextStyle {
411    fn to_json(&self) -> Value {
412        json!({
413            "color": self.color.to_json(),
414            "font_family": self.font_family_name.as_ref(),
415            "font_properties": self.font_properties.to_json(),
416        })
417    }
418}
419
420impl<'de> Deserialize<'de> for HighlightStyle {
421    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
422    where
423        D: serde::Deserializer<'de>,
424    {
425        let json = serde_json::Value::deserialize(deserializer)?;
426        if json.is_object() {
427            Ok(Self::from_json(
428                serde_json::from_value(json).map_err(de::Error::custom)?,
429            ))
430        } else {
431            Ok(Self {
432                color: serde_json::from_value(json).map_err(de::Error::custom)?,
433                ..Default::default()
434            })
435        }
436    }
437}
438
439fn underline_from_json(json: UnderlineStyleJson) -> Underline {
440    match json {
441        UnderlineStyleJson::Underlined(false) => Underline::default(),
442        UnderlineStyleJson::Underlined(true) => Underline {
443            color: None,
444            thickness: 1.0.into(),
445            squiggly: false,
446        },
447        UnderlineStyleJson::UnderlinedWithProperties {
448            color,
449            thickness,
450            squiggly,
451        } => Underline {
452            color,
453            thickness: thickness.unwrap_or(1.).into(),
454            squiggly,
455        },
456    }
457}
458
459fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
460    let weight = weight.map(weight_from_json).unwrap_or_default();
461    let style = if italic { Style::Italic } else { Style::Normal };
462    *Properties::new().weight(weight).style(style)
463}
464
465fn weight_from_json(weight: WeightJson) -> Weight {
466    match weight {
467        WeightJson::thin => Weight::THIN,
468        WeightJson::extra_light => Weight::EXTRA_LIGHT,
469        WeightJson::light => Weight::LIGHT,
470        WeightJson::normal => Weight::NORMAL,
471        WeightJson::medium => Weight::MEDIUM,
472        WeightJson::semibold => Weight::SEMIBOLD,
473        WeightJson::bold => Weight::BOLD,
474        WeightJson::extra_bold => Weight::EXTRA_BOLD,
475        WeightJson::black => Weight::BLACK,
476    }
477}
478
479impl ToJson for Properties {
480    fn to_json(&self) -> crate::json::Value {
481        json!({
482            "style": self.style.to_json(),
483            "weight": self.weight.to_json(),
484            "stretch": self.stretch.to_json(),
485        })
486    }
487}
488
489impl ToJson for Style {
490    fn to_json(&self) -> crate::json::Value {
491        match self {
492            Style::Normal => json!("normal"),
493            Style::Italic => json!("italic"),
494            Style::Oblique => json!("oblique"),
495        }
496    }
497}
498
499impl ToJson for Weight {
500    fn to_json(&self) -> crate::json::Value {
501        if self.0 == Weight::THIN.0 {
502            json!("thin")
503        } else if self.0 == Weight::EXTRA_LIGHT.0 {
504            json!("extra light")
505        } else if self.0 == Weight::LIGHT.0 {
506            json!("light")
507        } else if self.0 == Weight::NORMAL.0 {
508            json!("normal")
509        } else if self.0 == Weight::MEDIUM.0 {
510            json!("medium")
511        } else if self.0 == Weight::SEMIBOLD.0 {
512            json!("semibold")
513        } else if self.0 == Weight::BOLD.0 {
514            json!("bold")
515        } else if self.0 == Weight::EXTRA_BOLD.0 {
516            json!("extra bold")
517        } else if self.0 == Weight::BLACK.0 {
518            json!("black")
519        } else {
520            json!(self.0)
521        }
522    }
523}
524
525impl ToJson for Stretch {
526    fn to_json(&self) -> serde_json::Value {
527        json!(self.0)
528    }
529}
530
531pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
532where
533    F: FnOnce() -> T,
534{
535    FONT_CACHE.with(|cache| {
536        *cache.borrow_mut() = Some(font_cache);
537        let result = callback();
538        cache.borrow_mut().take();
539        result
540    })
541}