fonts.rs

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