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}