font_cache.rs

  1use crate::{
  2    fonts::{FontId, Metrics, Properties},
  3    geometry::vector::{vec2f, Vector2F},
  4    platform,
  5};
  6use anyhow::{anyhow, Result};
  7use parking_lot::{RwLock, RwLockUpgradableReadGuard};
  8use std::{collections::HashMap, sync::Arc};
  9
 10#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 11pub struct FamilyId(usize);
 12
 13struct Family {
 14    name: String,
 15    font_ids: Vec<FontId>,
 16}
 17
 18pub struct FontCache(RwLock<FontCacheState>);
 19
 20pub struct FontCacheState {
 21    fonts: Arc<dyn platform::FontSystem>,
 22    families: Vec<Family>,
 23    font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
 24    metrics: HashMap<FontId, Metrics>,
 25}
 26
 27unsafe impl Send for FontCache {}
 28
 29impl FontCache {
 30    pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
 31        Self(RwLock::new(FontCacheState {
 32            fonts,
 33            families: Vec::new(),
 34            font_selections: HashMap::new(),
 35            metrics: HashMap::new(),
 36        }))
 37    }
 38
 39    pub fn family_name(&self, family_id: FamilyId) -> Result<String> {
 40        self.0
 41            .read()
 42            .families
 43            .get(family_id.0)
 44            .ok_or_else(|| anyhow!("invalid family id"))
 45            .map(|family| family.name.clone())
 46    }
 47
 48    pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
 49        for name in names {
 50            let state = self.0.upgradable_read();
 51
 52            if let Some(ix) = state.families.iter().position(|f| f.name == *name) {
 53                return Ok(FamilyId(ix));
 54            }
 55
 56            let mut state = RwLockUpgradableReadGuard::upgrade(state);
 57
 58            if let Ok(font_ids) = state.fonts.load_family(name) {
 59                if font_ids.is_empty() {
 60                    continue;
 61                }
 62
 63                let family_id = FamilyId(state.families.len());
 64                for font_id in &font_ids {
 65                    if state.fonts.glyph_for_char(*font_id, 'm').is_none() {
 66                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
 67                    }
 68                }
 69
 70                state.families.push(Family {
 71                    name: String::from(*name),
 72                    font_ids,
 73                });
 74                return Ok(family_id);
 75            }
 76        }
 77
 78        Err(anyhow!(
 79            "could not find a non-empty font family matching one of the given names"
 80        ))
 81    }
 82
 83    pub fn default_font(&self, family_id: FamilyId) -> FontId {
 84        self.select_font(family_id, &Properties::default()).unwrap()
 85    }
 86
 87    pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result<FontId> {
 88        let inner = self.0.upgradable_read();
 89        if let Some(font_id) = inner
 90            .font_selections
 91            .get(&family_id)
 92            .and_then(|f| f.get(properties))
 93        {
 94            Ok(*font_id)
 95        } else {
 96            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
 97            let family = &inner.families[family_id.0];
 98            let font_id = inner
 99                .fonts
100                .select_font(&family.font_ids, properties)
101                .unwrap_or(family.font_ids[0]);
102
103            inner
104                .font_selections
105                .entry(family_id)
106                .or_default()
107                .insert(properties.clone(), font_id);
108            Ok(font_id)
109        }
110    }
111
112    pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
113    where
114        F: FnOnce(&Metrics) -> T,
115        T: 'static,
116    {
117        let state = self.0.upgradable_read();
118        if let Some(metrics) = state.metrics.get(&font_id) {
119            f(metrics)
120        } else {
121            let metrics = state.fonts.font_metrics(font_id);
122            let metric = f(&metrics);
123            let mut state = RwLockUpgradableReadGuard::upgrade(state);
124            state.metrics.insert(font_id, metrics);
125            metric
126        }
127    }
128
129    pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F {
130        let bounding_box = self.metric(font_id, |m| m.bounding_box);
131        let width = self.scale_metric(bounding_box.width(), font_id, font_size);
132        let height = self.scale_metric(bounding_box.height(), font_id, font_size);
133        vec2f(width, height)
134    }
135
136    pub fn em_width(&self, font_id: FontId, font_size: f32) -> f32 {
137        let state = self.0.read();
138        let glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
139        let bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap();
140        self.scale_metric(bounds.width(), font_id, font_size)
141    }
142
143    pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 {
144        let height = self.metric(font_id, |m| m.bounding_box.height());
145        self.scale_metric(height, font_id, font_size)
146    }
147
148    pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 {
149        self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size)
150    }
151
152    pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 {
153        self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size)
154    }
155
156    pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 {
157        self.scale_metric(self.metric(font_id, |m| -m.descent), font_id, font_size)
158    }
159
160    pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 {
161        metric * font_size / self.metric(font_id, |m| m.units_per_em as f32)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::{
169        fonts::{Style, Weight},
170        platform::{test, Platform as _},
171    };
172
173    #[test]
174    fn test_select_font() {
175        let platform = test::platform();
176        let fonts = FontCache::new(platform.fonts());
177        let arial = fonts.load_family(&["Arial"]).unwrap();
178        let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
179        let arial_italic = fonts
180            .select_font(arial, &Properties::new().style(Style::Italic))
181            .unwrap();
182        let arial_bold = fonts
183            .select_font(arial, &Properties::new().weight(Weight::BOLD))
184            .unwrap();
185        assert_ne!(arial_regular, arial_italic);
186        assert_ne!(arial_regular, arial_bold);
187        assert_ne!(arial_italic, arial_bold);
188    }
189}