font_cache.rs

  1use crate::{
  2    px, Bounds, FontFeatures, FontStyle, FontWeight, Pixels, PlatformTextSystem, Result, Size,
  3};
  4use anyhow::anyhow;
  5use parking_lot::{RwLock, RwLockUpgradableReadGuard};
  6use std::{collections::HashMap, sync::Arc};
  7
  8#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
  9pub struct FontFamilyId(usize);
 10
 11#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 12pub struct FontId(pub usize);
 13
 14pub(crate) struct FontCache(RwLock<FontCacheState>);
 15
 16pub(crate) struct FontCacheState {
 17    platform_text_system: Arc<dyn PlatformTextSystem>,
 18    families: Vec<Family>,
 19    default_family: Option<FontFamilyId>,
 20    font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
 21    metrics: HashMap<FontId, FontMetrics>,
 22}
 23
 24unsafe impl Send for FontCache {}
 25
 26impl FontCache {
 27    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
 28        Self(RwLock::new(FontCacheState {
 29            platform_text_system: fonts,
 30            families: Default::default(),
 31            default_family: None,
 32            font_selections: Default::default(),
 33            metrics: Default::default(),
 34        }))
 35    }
 36
 37    pub fn family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
 38        self.0
 39            .read()
 40            .families
 41            .get(family_id.0)
 42            .ok_or_else(|| anyhow!("invalid family id"))
 43            .map(|family| family.name.clone())
 44    }
 45
 46    pub fn load_family(&self, names: &[&str], features: &FontFeatures) -> Result<FontFamilyId> {
 47        for name in names {
 48            let state = self.0.upgradable_read();
 49
 50            if let Some(ix) = state
 51                .families
 52                .iter()
 53                .position(|f| f.name.as_ref() == *name && f.font_features == *features)
 54            {
 55                return Ok(FontFamilyId(ix));
 56            }
 57
 58            let mut state = RwLockUpgradableReadGuard::upgrade(state);
 59
 60            if let Ok(font_ids) = state.platform_text_system.load_family(name, features) {
 61                if font_ids.is_empty() {
 62                    continue;
 63                }
 64
 65                let family_id = FontFamilyId(state.families.len());
 66                for font_id in &font_ids {
 67                    if state
 68                        .platform_text_system
 69                        .glyph_for_char(*font_id, 'm')
 70                        .is_none()
 71                    {
 72                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
 73                    }
 74                }
 75
 76                state.families.push(Family {
 77                    name: Arc::from(*name),
 78                    font_features: features.clone(),
 79                    font_ids,
 80                });
 81                return Ok(family_id);
 82            }
 83        }
 84
 85        Err(anyhow!(
 86            "could not find a non-empty font family matching one of the given names"
 87        ))
 88    }
 89
 90    /// Returns an arbitrary font family that is available on the system.
 91    pub fn known_existing_family(&self) -> FontFamilyId {
 92        if let Some(family_id) = self.0.read().default_family {
 93            return family_id;
 94        }
 95
 96        let default_family = self
 97            .load_family(
 98                &["Courier", "Helvetica", "Arial", "Verdana"],
 99                &Default::default(),
100            )
101            .unwrap_or_else(|_| {
102                let all_family_names = self.0.read().platform_text_system.all_families();
103                let all_family_names: Vec<_> = all_family_names
104                    .iter()
105                    .map(|string| string.as_str())
106                    .collect();
107                self.load_family(&all_family_names, &Default::default())
108                    .expect("could not load any default font family")
109            });
110
111        self.0.write().default_family = Some(default_family);
112        default_family
113    }
114
115    pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
116        self.select_font(family_id, Default::default(), Default::default())
117            .unwrap()
118    }
119
120    pub fn select_font(
121        &self,
122        family_id: FontFamilyId,
123        weight: FontWeight,
124        style: FontStyle,
125    ) -> Result<FontId> {
126        let inner = self.0.upgradable_read();
127        if let Some(font_id) = inner
128            .font_selections
129            .get(&family_id)
130            .and_then(|fonts| fonts.get(&(weight, style)))
131        {
132            Ok(*font_id)
133        } else {
134            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
135            let family = &inner.families[family_id.0];
136            let font_id = inner
137                .platform_text_system
138                .select_font(&family.font_ids, weight, style)
139                .unwrap_or(family.font_ids[0]);
140            inner
141                .font_selections
142                .entry(family_id)
143                .or_default()
144                .insert((weight, style), font_id);
145            Ok(font_id)
146        }
147    }
148
149    pub fn read_metric<F, T>(&self, font_id: FontId, f: F) -> T
150    where
151        F: FnOnce(&FontMetrics) -> T,
152        T: 'static,
153    {
154        let state = self.0.upgradable_read();
155        if let Some(metrics) = state.metrics.get(&font_id) {
156            f(metrics)
157        } else {
158            let metrics = state.platform_text_system.font_metrics(font_id);
159            let metric = f(&metrics);
160            let mut state = RwLockUpgradableReadGuard::upgrade(state);
161            state.metrics.insert(font_id, metrics);
162            metric
163        }
164    }
165
166    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
167        let bounding_box = self.read_metric(font_id, |m| m.bounding_box);
168
169        let width = px(bounding_box.size.width) * self.em_size(font_id, font_size);
170        let height = px(bounding_box.size.height) * self.em_size(font_id, font_size);
171        Size { width, height }
172    }
173
174    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
175        let glyph_id;
176        let bounds;
177        {
178            let state = self.0.read();
179            glyph_id = state
180                .platform_text_system
181                .glyph_for_char(font_id, 'm')
182                .unwrap();
183            bounds = state
184                .platform_text_system
185                .typographic_bounds(font_id, glyph_id)
186                .unwrap();
187        }
188        self.em_size(font_id, font_size) * bounds.size.width
189    }
190
191    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
192        let glyph_id;
193        let advance;
194        {
195            let state = self.0.read();
196            glyph_id = state
197                .platform_text_system
198                .glyph_for_char(font_id, 'm')
199                .unwrap();
200            advance = state
201                .platform_text_system
202                .advance(font_id, glyph_id)
203                .unwrap();
204        }
205        self.em_size(font_id, font_size) * advance.width
206    }
207
208    pub fn line_height(&self, font_size: Pixels) -> Pixels {
209        (font_size * 1.618).round()
210    }
211
212    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
213        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.cap_height)
214    }
215
216    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
217        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.x_height)
218    }
219
220    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
221        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.ascent)
222    }
223
224    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
225        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| -m.descent)
226    }
227
228    pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
229        font_size / self.read_metric(font_id, |m| m.units_per_em as f32)
230    }
231
232    pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
233        let line_height = self.line_height(font_size);
234        let ascent = self.ascent(font_id, font_size);
235        let descent = self.descent(font_id, font_size);
236        let padding_top = (line_height - ascent - descent) / 2.;
237        padding_top + ascent
238    }
239}
240
241#[derive(Clone, Copy, Debug)]
242pub struct FontMetrics {
243    pub units_per_em: u32,
244    pub ascent: f32,
245    pub descent: f32,
246    pub line_gap: f32,
247    pub underline_position: f32,
248    pub underline_thickness: f32,
249    pub cap_height: f32,
250    pub x_height: f32,
251    pub bounding_box: Bounds<f32>,
252}
253
254struct Family {
255    name: Arc<str>,
256    font_features: FontFeatures,
257    font_ids: Vec<FontId>,
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::{FontStyle, FontWeight, Platform, TestPlatform};
264
265    #[test]
266    fn test_select_font() {
267        let platform = TestPlatform::new();
268        let fonts = FontCache::new(platform.text_system());
269        let arial = fonts
270            .load_family(
271                &["Arial"],
272                &FontFeatures {
273                    calt: Some(false),
274                    ..Default::default()
275                },
276            )
277            .unwrap();
278        let arial_regular = fonts
279            .select_font(arial, FontWeight::default(), FontStyle::default())
280            .unwrap();
281        let arial_italic = fonts
282            .select_font(arial, FontWeight::default(), FontStyle::Italic)
283            .unwrap();
284        let arial_bold = fonts
285            .select_font(arial, FontWeight::BOLD, FontStyle::default())
286            .unwrap();
287        assert_ne!(arial_regular, arial_italic);
288        assert_ne!(arial_regular, arial_bold);
289        assert_ne!(arial_italic, arial_bold);
290
291        let arial_with_calt = fonts
292            .load_family(
293                &["Arial"],
294                &FontFeatures {
295                    calt: Some(true),
296                    ..Default::default()
297                },
298            )
299            .unwrap();
300        assert_ne!(arial_with_calt, arial);
301    }
302}