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