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}