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// }