fonts.rs

  1use crate::geometry::{
  2    rect::RectI,
  3    vector::{vec2f, Vector2F, Vector2I},
  4};
  5use anyhow::{anyhow, Result};
  6use cocoa::appkit::CGPoint;
  7use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
  8use parking_lot::{RwLock, RwLockUpgradableReadGuard};
  9
 10pub use font_kit::properties::{Properties, Weight};
 11use font_kit::{
 12    font::Font, loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource,
 13};
 14use ordered_float::OrderedFloat;
 15use std::{collections::HashMap, sync::Arc};
 16
 17#[allow(non_upper_case_globals)]
 18const kCGImageAlphaOnly: u32 = 7;
 19
 20pub type GlyphId = u32;
 21
 22#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 23pub struct FamilyId(usize);
 24
 25#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 26pub struct FontId(usize);
 27
 28pub struct FontCache(RwLock<FontCacheState>);
 29
 30pub struct FontCacheState {
 31    source: SystemSource,
 32    families: Vec<Family>,
 33    fonts: Vec<Arc<Font>>,
 34    font_names: Vec<Arc<String>>,
 35    font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
 36    metrics: HashMap<FontId, Metrics>,
 37    native_fonts: HashMap<(FontId, OrderedFloat<f32>), NativeFont>,
 38    fonts_by_name: HashMap<Arc<String>, FontId>,
 39    emoji_font_id: Option<FontId>,
 40}
 41
 42unsafe impl Send for FontCache {}
 43
 44struct Family {
 45    name: String,
 46    font_ids: Vec<FontId>,
 47}
 48
 49impl FontCache {
 50    pub fn new() -> Self {
 51        Self(RwLock::new(FontCacheState {
 52            source: SystemSource::new(),
 53            families: Vec::new(),
 54            fonts: Vec::new(),
 55            font_names: Vec::new(),
 56            font_selections: HashMap::new(),
 57            metrics: HashMap::new(),
 58            native_fonts: HashMap::new(),
 59            fonts_by_name: HashMap::new(),
 60            emoji_font_id: None,
 61        }))
 62    }
 63
 64    pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
 65        for name in names {
 66            let state = self.0.upgradable_read();
 67
 68            if let Some(ix) = state.families.iter().position(|f| f.name == *name) {
 69                return Ok(FamilyId(ix));
 70            }
 71
 72            let mut state = RwLockUpgradableReadGuard::upgrade(state);
 73
 74            if let Ok(handle) = state.source.select_family_by_name(name) {
 75                if handle.is_empty() {
 76                    continue;
 77                }
 78
 79                let family_id = FamilyId(state.families.len());
 80                let mut font_ids = Vec::new();
 81                for font in handle.fonts() {
 82                    let font = font.load()?;
 83                    if font.glyph_for_char('m').is_none() {
 84                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
 85                    }
 86                    font_ids.push(push_font(&mut state, font));
 87                }
 88
 89                state.families.push(Family {
 90                    name: String::from(*name),
 91                    font_ids,
 92                });
 93                return Ok(family_id);
 94            }
 95        }
 96
 97        Err(anyhow!(
 98            "could not find a non-empty font family matching one of the given names"
 99        ))
100    }
101
102    pub fn default_font(&self, family_id: FamilyId) -> FontId {
103        self.select_font(family_id, &Properties::default()).unwrap()
104    }
105
106    pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result<FontId> {
107        let inner = self.0.upgradable_read();
108        if let Some(font_id) = inner
109            .font_selections
110            .get(&family_id)
111            .and_then(|f| f.get(properties))
112        {
113            Ok(*font_id)
114        } else {
115            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
116            let family = &inner.families[family_id.0];
117            let candidates = family
118                .font_ids
119                .iter()
120                .map(|font_id| inner.fonts[font_id.0].properties())
121                .collect::<Vec<_>>();
122            let idx = font_kit::matching::find_best_match(&candidates, properties)?;
123            let font_id = family.font_ids[idx];
124
125            inner
126                .font_selections
127                .entry(family_id)
128                .or_default()
129                .insert(properties.clone(), font_id);
130            Ok(font_id)
131        }
132    }
133
134    pub fn font(&self, font_id: FontId) -> Arc<Font> {
135        self.0.read().fonts[font_id.0].clone()
136    }
137
138    pub fn font_name(&self, font_id: FontId) -> Arc<String> {
139        self.0.read().font_names[font_id.0].clone()
140    }
141
142    pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
143    where
144        F: FnOnce(&Metrics) -> T,
145        T: 'static,
146    {
147        let state = self.0.upgradable_read();
148        if let Some(metrics) = state.metrics.get(&font_id) {
149            f(metrics)
150        } else {
151            let metrics = state.fonts[font_id.0].metrics();
152            let metric = f(&metrics);
153            let mut state = RwLockUpgradableReadGuard::upgrade(state);
154            state.metrics.insert(font_id, metrics);
155            metric
156        }
157    }
158
159    pub fn is_emoji(&self, font_id: FontId) -> bool {
160        self.0
161            .read()
162            .emoji_font_id
163            .map_or(false, |emoji_font_id| emoji_font_id == font_id)
164    }
165
166    pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F {
167        let bounding_box = self.metric(font_id, |m| m.bounding_box);
168        let width = self.scale_metric(bounding_box.width(), font_id, font_size);
169        let height = self.scale_metric(bounding_box.height(), font_id, font_size);
170        vec2f(width, height)
171    }
172
173    pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 {
174        let bounding_box = self.metric(font_id, |m| m.bounding_box);
175        self.scale_metric(bounding_box.height(), font_id, font_size)
176    }
177
178    pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 {
179        self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size)
180    }
181
182    pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 {
183        self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size)
184    }
185
186    pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 {
187        self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size)
188    }
189
190    pub fn render_glyph(
191        &self,
192        font_id: FontId,
193        font_size: f32,
194        glyph_id: GlyphId,
195        scale_factor: f32,
196    ) -> Option<(Vector2I, Vec<u8>)> {
197        let native_font = self.native_font(font_id, font_size);
198        let glyph_id = glyph_id as CGGlyph;
199        let glyph_bounds =
200            native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph_id]);
201        let position = CGPoint::new(-glyph_bounds.origin.x, -glyph_bounds.origin.y);
202        let width = (glyph_bounds.size.width * scale_factor as f64).ceil() as usize;
203        let height = (glyph_bounds.size.height * scale_factor as f64).ceil() as usize;
204
205        if width == 0 || height == 0 {
206            None
207        } else {
208            let mut ctx = CGContext::create_bitmap_context(
209                None,
210                width,
211                height,
212                8,
213                width,
214                &CGColorSpace::create_device_gray(),
215                kCGImageAlphaOnly,
216            );
217            ctx.scale(scale_factor as f64, scale_factor as f64);
218            native_font.draw_glyphs(&[glyph_id], &[position], ctx.clone());
219            ctx.flush();
220
221            Some((
222                Vector2I::new(width as i32, height as i32),
223                Vec::from(ctx.data()),
224            ))
225        }
226    }
227
228    fn emoji_font_id(&self) -> Result<FontId> {
229        let state = self.0.upgradable_read();
230
231        if let Some(font_id) = state.emoji_font_id {
232            Ok(font_id)
233        } else {
234            let handle = state.source.select_family_by_name("Apple Color Emoji")?;
235            let font = handle
236                .fonts()
237                .first()
238                .ok_or(anyhow!("no fonts in Apple Color Emoji font family"))?
239                .load()?;
240            let mut state = RwLockUpgradableReadGuard::upgrade(state);
241            let font_id = push_font(&mut state, font);
242            state.emoji_font_id = Some(font_id);
243            Ok(font_id)
244        }
245    }
246
247    pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 {
248        metric * font_size / self.metric(font_id, |m| m.units_per_em as f32)
249    }
250
251    pub fn native_font(&self, font_id: FontId, size: f32) -> NativeFont {
252        let native_key = (font_id, OrderedFloat(size));
253
254        let state = self.0.upgradable_read();
255        if let Some(native_font) = state.native_fonts.get(&native_key).cloned() {
256            native_font
257        } else {
258            let native_font = state.fonts[font_id.0]
259                .native_font()
260                .clone_with_font_size(size as f64);
261            RwLockUpgradableReadGuard::upgrade(state)
262                .native_fonts
263                .insert(native_key, native_font.clone());
264            native_font
265        }
266    }
267
268    pub fn font_id_for_native_font(&self, native_font: NativeFont) -> FontId {
269        let postscript_name = native_font.postscript_name();
270        let state = self.0.upgradable_read();
271        if let Some(font_id) = state.fonts_by_name.get(&postscript_name) {
272            *font_id
273        } else {
274            push_font(&mut RwLockUpgradableReadGuard::upgrade(state), unsafe {
275                Font::from_native_font(native_font.clone())
276            })
277        }
278    }
279}
280
281fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
282    let font_id = FontId(state.fonts.len());
283    let name = Arc::new(font.postscript_name().unwrap());
284    if *name == "AppleColorEmoji" {
285        state.emoji_font_id = Some(font_id);
286    }
287    state.fonts.push(Arc::new(font));
288    state.font_names.push(name.clone());
289    state.fonts_by_name.insert(name, font_id);
290    font_id
291}
292
293#[cfg(test)]
294mod tests {
295    use std::{fs::File, io::BufWriter, path::Path};
296
297    use super::*;
298
299    #[test]
300    fn test_render_glyph() {
301        let cache = FontCache::new();
302        let family_id = cache.load_family(&["Fira Code"]).unwrap();
303        let font_id = cache.select_font(family_id, &Default::default()).unwrap();
304        let glyph_id = cache.font(font_id).glyph_for_char('m').unwrap();
305        let (size, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap();
306
307        let path = Path::new(r"/Users/as-cii/Desktop/image.png");
308        let file = File::create(path).unwrap();
309        let ref mut w = BufWriter::new(file);
310
311        let mut encoder = png::Encoder::new(w, size.x() as u32, size.y() as u32);
312        encoder.set_color(png::ColorType::Grayscale);
313        encoder.set_depth(png::BitDepth::Eight);
314        let mut writer = encoder.write_header().unwrap();
315
316        writer.write_image_data(&bytes).unwrap(); // Save
317        dbg!(size, bytes);
318    }
319}