1use crate::{
2 fonts::{FontId, GlyphId, Metrics, Properties},
3 geometry::{
4 rect::{RectF, RectI},
5 transform2d::Transform2F,
6 vector::{vec2f, vec2i, Vector2F},
7 },
8 platform,
9 text_layout::{Glyph, Line, Run},
10};
11use cocoa::appkit::{CGFloat, CGPoint};
12use core_foundation::{
13 attributed_string::CFMutableAttributedString,
14 base::{CFRange, TCFType},
15 number::CFNumber,
16 string::CFString,
17};
18use core_graphics::{
19 base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform,
20};
21use core_text::{line::CTLine, string_attributes::kCTFontAttributeName};
22use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource};
23use parking_lot::RwLock;
24use std::{char, convert::TryFrom};
25
26#[allow(non_upper_case_globals)]
27const kCGImageAlphaOnly: u32 = 7;
28
29pub struct FontSystem(RwLock<FontSystemState>);
30
31struct FontSystemState {
32 source: SystemSource,
33 fonts: Vec<font_kit::font::Font>,
34}
35
36impl FontSystem {
37 pub fn new() -> Self {
38 Self(RwLock::new(FontSystemState {
39 source: SystemSource::new(),
40 fonts: Vec::new(),
41 }))
42 }
43}
44
45impl platform::FontSystem for FontSystem {
46 fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>> {
47 self.0.write().load_family(name)
48 }
49
50 fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
51 self.0.read().select_font(font_ids, properties)
52 }
53
54 fn font_metrics(&self, font_id: FontId) -> Metrics {
55 self.0.read().font_metrics(font_id)
56 }
57
58 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
59 self.0.read().typographic_bounds(font_id, glyph_id)
60 }
61
62 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
63 self.0.read().glyph_for_char(font_id, ch)
64 }
65
66 fn rasterize_glyph(
67 &self,
68 font_id: FontId,
69 font_size: f32,
70 glyph_id: GlyphId,
71 subpixel_shift: Vector2F,
72 scale_factor: f32,
73 ) -> Option<(RectI, Vec<u8>)> {
74 self.0
75 .read()
76 .rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor)
77 }
78
79 fn layout_str(
80 &self,
81 text: &str,
82 font_size: f32,
83 runs: &[(std::ops::Range<usize>, FontId)],
84 ) -> Line {
85 self.0.read().layout_str(text, font_size, runs)
86 }
87}
88
89impl FontSystemState {
90 fn load_family(&mut self, name: &str) -> anyhow::Result<Vec<FontId>> {
91 let mut font_ids = Vec::new();
92 for font in self.source.select_family_by_name(name)?.fonts() {
93 let font = font.load()?;
94 font_ids.push(FontId(self.fonts.len()));
95 self.fonts.push(font);
96 }
97 Ok(font_ids)
98 }
99
100 fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
101 let candidates = font_ids
102 .iter()
103 .map(|font_id| self.fonts[font_id.0].properties())
104 .collect::<Vec<_>>();
105 let idx = font_kit::matching::find_best_match(&candidates, properties)?;
106 Ok(font_ids[idx])
107 }
108
109 fn font_metrics(&self, font_id: FontId) -> Metrics {
110 self.fonts[font_id.0].metrics()
111 }
112
113 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
114 Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
115 }
116
117 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
118 self.fonts[font_id.0].glyph_for_char(ch)
119 }
120
121 fn rasterize_glyph(
122 &self,
123 font_id: FontId,
124 font_size: f32,
125 glyph_id: GlyphId,
126 subpixel_shift: Vector2F,
127 scale_factor: f32,
128 ) -> Option<(RectI, Vec<u8>)> {
129 let font = &self.fonts[font_id.0];
130 let scale = Transform2F::from_scale(scale_factor);
131 let bounds = font
132 .raster_bounds(
133 glyph_id,
134 font_size,
135 scale,
136 HintingOptions::None,
137 RasterizationOptions::GrayscaleAa,
138 )
139 .ok()?;
140
141 if bounds.width() == 0 || bounds.height() == 0 {
142 None
143 } else {
144 // Make room for subpixel variants.
145 let bounds = RectI::new(bounds.origin(), bounds.size() + vec2i(1, 1));
146 let mut pixels = vec![0; bounds.width() as usize * bounds.height() as usize];
147 let ctx = CGContext::create_bitmap_context(
148 Some(pixels.as_mut_ptr() as *mut _),
149 bounds.width() as usize,
150 bounds.height() as usize,
151 8,
152 bounds.width() as usize,
153 &CGColorSpace::create_device_gray(),
154 kCGImageAlphaOnly,
155 );
156
157 // Move the origin to bottom left and account for scaling, this
158 // makes drawing text consistent with the font-kit's raster_bounds.
159 ctx.translate(0.0, bounds.height() as CGFloat);
160 let transform = scale.translate(-bounds.origin().to_f32());
161 ctx.set_text_matrix(&CGAffineTransform {
162 a: transform.matrix.m11() as CGFloat,
163 b: -transform.matrix.m21() as CGFloat,
164 c: -transform.matrix.m12() as CGFloat,
165 d: transform.matrix.m22() as CGFloat,
166 tx: transform.vector.x() as CGFloat,
167 ty: -transform.vector.y() as CGFloat,
168 });
169
170 ctx.set_font(&font.native_font().copy_to_CGFont());
171 ctx.set_font_size(font_size as CGFloat);
172 ctx.show_glyphs_at_positions(
173 &[glyph_id as CGGlyph],
174 &[CGPoint::new(
175 (subpixel_shift.x() / scale_factor) as CGFloat,
176 (subpixel_shift.y() / scale_factor) as CGFloat,
177 )],
178 );
179
180 Some((bounds, pixels))
181 }
182 }
183
184 fn layout_str(
185 &self,
186 text: &str,
187 font_size: f32,
188 runs: &[(std::ops::Range<usize>, FontId)],
189 ) -> Line {
190 let font_id_attr_name = CFString::from_static_string("zed_font_id");
191
192 let mut string = CFMutableAttributedString::new();
193 string.replace_str(&CFString::new(text), CFRange::init(0, 0));
194
195 let mut utf16_lens = text.chars().map(|c| c.len_utf16());
196 let mut prev_char_ix = 0;
197 let mut prev_utf16_ix = 0;
198
199 for (range, font_id) in runs {
200 let utf16_start = prev_utf16_ix
201 + utf16_lens
202 .by_ref()
203 .take(range.start - prev_char_ix)
204 .sum::<usize>();
205 let utf16_end = utf16_start
206 + utf16_lens
207 .by_ref()
208 .take(range.end - range.start)
209 .sum::<usize>();
210 prev_char_ix = range.end;
211 prev_utf16_ix = utf16_end;
212
213 let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
214 let font = &self.fonts[font_id.0];
215 unsafe {
216 string.set_attribute(
217 cf_range,
218 kCTFontAttributeName,
219 &font.native_font().clone_with_font_size(font_size as f64),
220 );
221 string.set_attribute(
222 cf_range,
223 font_id_attr_name.as_concrete_TypeRef(),
224 &CFNumber::from(font_id.0 as i64),
225 );
226 }
227 }
228
229 let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
230
231 let mut utf16_chars = text.encode_utf16();
232 let mut char_ix = 0;
233 let mut prev_utf16_ix = 0;
234
235 let mut runs = Vec::new();
236 for run in line.glyph_runs().into_iter() {
237 let font_id = FontId(
238 run.attributes()
239 .unwrap()
240 .get(&font_id_attr_name)
241 .downcast::<CFNumber>()
242 .unwrap()
243 .to_i64()
244 .unwrap() as usize,
245 );
246
247 let mut glyphs = Vec::new();
248 for ((glyph_id, position), utf16_ix) in run
249 .glyphs()
250 .iter()
251 .zip(run.positions().iter())
252 .zip(run.string_indices().iter())
253 {
254 let utf16_ix = usize::try_from(*utf16_ix).unwrap();
255 char_ix +=
256 char::decode_utf16(utf16_chars.by_ref().take(utf16_ix - prev_utf16_ix)).count();
257 prev_utf16_ix = utf16_ix;
258
259 glyphs.push(Glyph {
260 id: *glyph_id as GlyphId,
261 position: vec2f(position.x as f32, position.y as f32),
262 index: char_ix,
263 });
264 }
265
266 runs.push(Run { font_id, glyphs })
267 }
268
269 let typographic_bounds = line.get_typographic_bounds();
270 Line {
271 width: typographic_bounds.width as f32,
272 ascent: typographic_bounds.ascent as f32,
273 descent: typographic_bounds.descent as f32,
274 runs,
275 font_size,
276 len: char_ix + 1,
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use font_kit::properties::{Style, Weight};
285 use platform::FontSystem as _;
286
287 #[test]
288 fn test_layout_str() -> anyhow::Result<()> {
289 let fonts = FontSystem::new();
290 let menlo = fonts.load_family("Menlo")?;
291 let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
292 let menlo_italic = fonts.select_font(&menlo, &Properties::new().style(Style::Italic))?;
293 let menlo_bold = fonts.select_font(&menlo, &Properties::new().weight(Weight::BOLD))?;
294
295 let line = fonts.layout_str(
296 "hello world",
297 16.0,
298 &[
299 (0..2, menlo_bold),
300 (2..6, menlo_italic),
301 (6..11, menlo_regular),
302 ],
303 );
304 assert_eq!(line.runs.len(), 3);
305 assert_eq!(line.runs[0].font_id, menlo_bold);
306 assert_eq!(line.runs[0].glyphs.len(), 2);
307 assert_eq!(line.runs[1].font_id, menlo_italic);
308 assert_eq!(line.runs[1].glyphs.len(), 4);
309 assert_eq!(line.runs[2].font_id, menlo_regular);
310 assert_eq!(line.runs[2].glyphs.len(), 5);
311 Ok(())
312 }
313
314 #[test]
315 fn test_char_indices() -> anyhow::Result<()> {
316 let fonts = FontSystem::new();
317 let zapfino = fonts.load_family("Zapfino")?;
318 let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?;
319 let menlo = fonts.load_family("Menlo")?;
320 let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
321
322 let text = "This is, mπre πr less, Zapfino!π";
323 let line = fonts.layout_str(
324 text,
325 16.0,
326 &[
327 (0..9, zapfino_regular),
328 (9..22, menlo_regular),
329 (22..text.encode_utf16().count(), zapfino_regular),
330 ],
331 );
332 assert_eq!(
333 line.runs
334 .iter()
335 .flat_map(|r| r.glyphs.iter())
336 .map(|g| g.index)
337 .collect::<Vec<_>>(),
338 vec![
339 0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31
340 ]
341 );
342 Ok(())
343 }
344
345 // #[test]
346 // fn test_rasterize_glyph() {
347 // use std::{fs::File, io::BufWriter, path::Path};
348
349 // let fonts = FontSystem::new();
350 // let font_ids = fonts.load_family("Fira Code").unwrap();
351 // let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
352 // let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
353
354 // const VARIANTS: usize = 1;
355 // for i in 0..VARIANTS {
356 // let variant = i as f32 / VARIANTS as f32;
357 // let (bounds, bytes) = fonts
358 // .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.)
359 // .unwrap();
360
361 // let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
362 // let path = Path::new(&name);
363 // let file = File::create(path).unwrap();
364 // let ref mut w = BufWriter::new(file);
365
366 // let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
367 // encoder.set_color(png::ColorType::Grayscale);
368 // encoder.set_depth(png::BitDepth::Eight);
369 // let mut writer = encoder.write_header().unwrap();
370 // writer.write_image_data(&bytes).unwrap();
371 // }
372 // }
373}