1use crate::{
2 fonts::{FontId, GlyphId, Metrics, Properties},
3 geometry::{
4 rect::{RectF, RectI},
5 transform2d::Transform2F,
6 vector::{vec2f, Vector2F},
7 },
8 platform::{self, RasterizationOptions},
9 text_layout::{Glyph, LineLayout, Run, RunStyle},
10};
11use cocoa::appkit::{CGFloat, CGPoint};
12use core_foundation::{
13 array::CFIndex,
14 attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
15 base::{CFRange, TCFType},
16 string::CFString,
17};
18use core_graphics::{
19 base::{kCGImageAlphaPremultipliedLast, CGGlyph},
20 color_space::CGColorSpace,
21 context::CGContext,
22};
23use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
24use font_kit::{
25 handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
26};
27use parking_lot::RwLock;
28use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
29
30#[allow(non_upper_case_globals)]
31const kCGImageAlphaOnly: u32 = 7;
32
33pub struct FontSystem(RwLock<FontSystemState>);
34
35struct FontSystemState {
36 memory_source: MemSource,
37 system_source: SystemSource,
38 fonts: Vec<font_kit::font::Font>,
39 emoji_font_id: FontId,
40}
41
42impl FontSystem {
43 pub fn new() -> Self {
44 let mut state = FontSystemState {
45 memory_source: MemSource::empty(),
46 system_source: SystemSource::new(),
47 fonts: Vec::new(),
48 emoji_font_id: FontId(0), // This will be the first font that we load.
49 };
50 state.load_family("Apple Color Emoji").unwrap();
51 Self(RwLock::new(state))
52 }
53}
54
55impl platform::FontSystem for FontSystem {
56 fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
57 self.0.write().add_fonts(fonts)
58 }
59
60 fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>> {
61 self.0.write().load_family(name)
62 }
63
64 fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
65 self.0.read().select_font(font_ids, properties)
66 }
67
68 fn font_metrics(&self, font_id: FontId) -> Metrics {
69 self.0.read().font_metrics(font_id)
70 }
71
72 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
73 self.0.read().typographic_bounds(font_id, glyph_id)
74 }
75
76 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
77 self.0.read().advance(font_id, glyph_id)
78 }
79
80 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
81 self.0.read().glyph_for_char(font_id, ch)
82 }
83
84 fn rasterize_glyph(
85 &self,
86 font_id: FontId,
87 font_size: f32,
88 glyph_id: GlyphId,
89 subpixel_shift: Vector2F,
90 scale_factor: f32,
91 options: RasterizationOptions,
92 ) -> Option<(RectI, Vec<u8>)> {
93 self.0.read().rasterize_glyph(
94 font_id,
95 font_size,
96 glyph_id,
97 subpixel_shift,
98 scale_factor,
99 options,
100 )
101 }
102
103 fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
104 self.0.write().layout_line(text, font_size, runs)
105 }
106
107 fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
108 self.0.read().wrap_line(text, font_id, font_size, width)
109 }
110}
111
112impl FontSystemState {
113 fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
114 self.memory_source.add_fonts(
115 fonts
116 .iter()
117 .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
118 )?;
119 Ok(())
120 }
121
122 fn load_family(&mut self, name: &str) -> anyhow::Result<Vec<FontId>> {
123 let mut font_ids = Vec::new();
124
125 let family = self
126 .memory_source
127 .select_family_by_name(name)
128 .or_else(|_| self.system_source.select_family_by_name(name))?;
129 for font in family.fonts() {
130 let font = font.load()?;
131 font_ids.push(FontId(self.fonts.len()));
132 self.fonts.push(font);
133 }
134 Ok(font_ids)
135 }
136
137 fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
138 let candidates = font_ids
139 .iter()
140 .map(|font_id| self.fonts[font_id.0].properties())
141 .collect::<Vec<_>>();
142 let idx = font_kit::matching::find_best_match(&candidates, properties)?;
143 Ok(font_ids[idx])
144 }
145
146 fn font_metrics(&self, font_id: FontId) -> Metrics {
147 self.fonts[font_id.0].metrics()
148 }
149
150 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
151 Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
152 }
153
154 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
155 Ok(self.fonts[font_id.0].advance(glyph_id)?)
156 }
157
158 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
159 self.fonts[font_id.0].glyph_for_char(ch)
160 }
161
162 fn id_for_font(&mut self, requested_font: font_kit::font::Font) -> FontId {
163 // TODO: don't allocate the postscript name
164 // Note: Coretext always returns a Some option for postscript_name
165 let requested_font_name = requested_font.postscript_name();
166 for (id, font) in self.fonts.iter().enumerate() {
167 if font.postscript_name() == requested_font_name {
168 return FontId(id);
169 }
170 }
171 self.fonts.push(requested_font);
172 FontId(self.fonts.len() - 1)
173 }
174
175 fn rasterize_glyph(
176 &self,
177 font_id: FontId,
178 font_size: f32,
179 glyph_id: GlyphId,
180 subpixel_shift: Vector2F,
181 scale_factor: f32,
182 options: RasterizationOptions,
183 ) -> Option<(RectI, Vec<u8>)> {
184 let font = &self.fonts[font_id.0];
185 let scale = Transform2F::from_scale(scale_factor);
186 let glyph_bounds = font
187 .raster_bounds(
188 glyph_id,
189 font_size,
190 scale,
191 HintingOptions::None,
192 font_kit::canvas::RasterizationOptions::GrayscaleAa,
193 )
194 .ok()?;
195
196 if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
197 None
198 } else {
199 // Make room for subpixel variants.
200 let subpixel_padding = subpixel_shift.ceil().to_i32();
201 let cx_bounds = RectI::new(
202 glyph_bounds.origin(),
203 glyph_bounds.size() + subpixel_padding,
204 );
205
206 let mut bytes;
207 let cx;
208 match options {
209 RasterizationOptions::Alpha => {
210 bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
211 cx = CGContext::create_bitmap_context(
212 Some(bytes.as_mut_ptr() as *mut _),
213 cx_bounds.width() as usize,
214 cx_bounds.height() as usize,
215 8,
216 cx_bounds.width() as usize,
217 &CGColorSpace::create_device_gray(),
218 kCGImageAlphaOnly,
219 );
220 }
221 RasterizationOptions::Bgra => {
222 bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
223 cx = CGContext::create_bitmap_context(
224 Some(bytes.as_mut_ptr() as *mut _),
225 cx_bounds.width() as usize,
226 cx_bounds.height() as usize,
227 8,
228 cx_bounds.width() as usize * 4,
229 &CGColorSpace::create_device_rgb(),
230 kCGImageAlphaPremultipliedLast,
231 );
232 }
233 }
234
235 // Move the origin to bottom left and account for scaling, this
236 // makes drawing text consistent with the font-kit's raster_bounds.
237 cx.translate(
238 -glyph_bounds.origin_x() as CGFloat,
239 (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
240 );
241 cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
242
243 cx.set_allows_font_subpixel_positioning(true);
244 cx.set_should_subpixel_position_fonts(true);
245 cx.set_allows_font_subpixel_quantization(false);
246 cx.set_should_subpixel_quantize_fonts(false);
247 font.native_font()
248 .clone_with_font_size(font_size as CGFloat)
249 .draw_glyphs(
250 &[glyph_id as CGGlyph],
251 &[CGPoint::new(
252 (subpixel_shift.x() / scale_factor) as CGFloat,
253 (subpixel_shift.y() / scale_factor) as CGFloat,
254 )],
255 cx,
256 );
257
258 if let RasterizationOptions::Bgra = options {
259 // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
260 for pixel in bytes.chunks_exact_mut(4) {
261 pixel.swap(0, 2);
262 let a = pixel[3] as f32 / 255.;
263 pixel[0] = (pixel[0] as f32 / a) as u8;
264 pixel[1] = (pixel[1] as f32 / a) as u8;
265 pixel[2] = (pixel[2] as f32 / a) as u8;
266 }
267 }
268
269 Some((cx_bounds, bytes))
270 }
271 }
272
273 fn layout_line(
274 &mut self,
275 text: &str,
276 font_size: f32,
277 runs: &[(usize, RunStyle)],
278 ) -> LineLayout {
279 // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
280 let mut string = CFMutableAttributedString::new();
281 {
282 string.replace_str(&CFString::new(text), CFRange::init(0, 0));
283 let utf16_line_len = string.char_len() as usize;
284
285 let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
286 let font_runs = runs
287 .iter()
288 .filter_map(|(len, style)| {
289 let mut last_run = last_run.borrow_mut();
290 if let Some((last_len, last_font_id)) = last_run.as_mut() {
291 if style.font_id == *last_font_id {
292 *last_len += *len;
293 None
294 } else {
295 let result = (*last_len, *last_font_id);
296 *last_len = *len;
297 *last_font_id = style.font_id;
298 Some(result)
299 }
300 } else {
301 *last_run = Some((*len, style.font_id));
302 None
303 }
304 })
305 .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
306
307 let mut ix_converter = StringIndexConverter::new(text);
308 for (run_len, font_id) in font_runs {
309 let utf8_end = ix_converter.utf8_ix + run_len;
310 let utf16_start = ix_converter.utf16_ix;
311
312 if utf16_start >= utf16_line_len {
313 break;
314 }
315
316 ix_converter.advance_to_utf8_ix(utf8_end);
317 let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
318
319 let cf_range =
320 CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
321 let font = &self.fonts[font_id.0];
322 unsafe {
323 string.set_attribute(
324 cf_range,
325 kCTFontAttributeName,
326 &font.native_font().clone_with_font_size(font_size as f64),
327 );
328 }
329
330 if utf16_end == utf16_line_len {
331 break;
332 }
333 }
334 }
335
336 // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
337 let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
338
339 let mut runs = Vec::new();
340 for run in line.glyph_runs().into_iter() {
341 let attributes = run.attributes().unwrap();
342 let font = unsafe {
343 let native_font = attributes
344 .get(kCTFontAttributeName)
345 .downcast::<CTFont>()
346 .unwrap();
347 font_kit::font::Font::from_native_font(native_font)
348 };
349 let font_id = self.id_for_font(font);
350
351 let mut ix_converter = StringIndexConverter::new(text);
352 let mut glyphs = Vec::new();
353 for ((glyph_id, position), glyph_utf16_ix) in run
354 .glyphs()
355 .iter()
356 .zip(run.positions().iter())
357 .zip(run.string_indices().iter())
358 {
359 let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
360 ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
361 glyphs.push(Glyph {
362 id: *glyph_id as GlyphId,
363 position: vec2f(position.x as f32, position.y as f32),
364 index: ix_converter.utf8_ix,
365 is_emoji: font_id == self.emoji_font_id,
366 });
367 }
368
369 runs.push(Run { font_id, glyphs })
370 }
371
372 let typographic_bounds = line.get_typographic_bounds();
373 LineLayout {
374 width: typographic_bounds.width as f32,
375 ascent: typographic_bounds.ascent as f32,
376 descent: typographic_bounds.descent as f32,
377 runs,
378 font_size,
379 len: text.len(),
380 }
381 }
382
383 fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
384 let mut string = CFMutableAttributedString::new();
385 string.replace_str(&CFString::new(text), CFRange::init(0, 0));
386 let cf_range = CFRange::init(0 as isize, text.encode_utf16().count() as isize);
387 let font = &self.fonts[font_id.0];
388 unsafe {
389 string.set_attribute(
390 cf_range,
391 kCTFontAttributeName,
392 &font.native_font().clone_with_font_size(font_size as f64),
393 );
394
395 let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
396 let mut ix_converter = StringIndexConverter::new(text);
397 let mut break_indices = Vec::new();
398 while ix_converter.utf8_ix < text.len() {
399 let utf16_len = CTTypesetterSuggestLineBreak(
400 typesetter,
401 ix_converter.utf16_ix as isize,
402 width as f64,
403 ) as usize;
404 ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
405 if ix_converter.utf8_ix >= text.len() {
406 break;
407 }
408 break_indices.push(ix_converter.utf8_ix as usize);
409 }
410 break_indices
411 }
412 }
413}
414
415#[derive(Clone)]
416struct StringIndexConverter<'a> {
417 text: &'a str,
418 utf8_ix: usize,
419 utf16_ix: usize,
420}
421
422impl<'a> StringIndexConverter<'a> {
423 fn new(text: &'a str) -> Self {
424 Self {
425 text,
426 utf8_ix: 0,
427 utf16_ix: 0,
428 }
429 }
430
431 fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
432 for (ix, c) in self.text[self.utf8_ix..].char_indices() {
433 if self.utf8_ix + ix >= utf8_target {
434 self.utf8_ix += ix;
435 return;
436 }
437 self.utf16_ix += c.len_utf16();
438 }
439 self.utf8_ix = self.text.len();
440 }
441
442 fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
443 for (ix, c) in self.text[self.utf8_ix..].char_indices() {
444 if self.utf16_ix >= utf16_target {
445 self.utf8_ix += ix;
446 return;
447 }
448 self.utf16_ix += c.len_utf16();
449 }
450 self.utf8_ix = self.text.len();
451 }
452}
453
454#[repr(C)]
455pub struct __CFTypesetter(c_void);
456
457pub type CTTypesetterRef = *const __CFTypesetter;
458
459#[link(name = "CoreText", kind = "framework")]
460extern "C" {
461 fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
462
463 fn CTTypesetterSuggestLineBreak(
464 typesetter: CTTypesetterRef,
465 start_index: CFIndex,
466 width: f64,
467 ) -> CFIndex;
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::MutableAppContext;
474 use font_kit::properties::{Style, Weight};
475 use platform::FontSystem as _;
476
477 #[crate::test(self, retries = 5)]
478 fn test_layout_str(_: &mut MutableAppContext) {
479 // This is failing intermittently on CI and we don't have time to figure it out
480 let fonts = FontSystem::new();
481 let menlo = fonts.load_family("Menlo").unwrap();
482 let menlo_regular = RunStyle {
483 font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
484 color: Default::default(),
485 underline: Default::default(),
486 };
487 let menlo_italic = RunStyle {
488 font_id: fonts
489 .select_font(&menlo, &Properties::new().style(Style::Italic))
490 .unwrap(),
491 color: Default::default(),
492 underline: Default::default(),
493 };
494 let menlo_bold = RunStyle {
495 font_id: fonts
496 .select_font(&menlo, &Properties::new().weight(Weight::BOLD))
497 .unwrap(),
498 color: Default::default(),
499 underline: Default::default(),
500 };
501 assert_ne!(menlo_regular, menlo_italic);
502 assert_ne!(menlo_regular, menlo_bold);
503 assert_ne!(menlo_italic, menlo_bold);
504
505 let line = fonts.layout_line(
506 "hello world",
507 16.0,
508 &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
509 );
510 assert_eq!(line.runs.len(), 3);
511 assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
512 assert_eq!(line.runs[0].glyphs.len(), 2);
513 assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
514 assert_eq!(line.runs[1].glyphs.len(), 4);
515 assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
516 assert_eq!(line.runs[2].glyphs.len(), 5);
517 }
518
519 #[test]
520 fn test_glyph_offsets() -> anyhow::Result<()> {
521 let fonts = FontSystem::new();
522 let zapfino = fonts.load_family("Zapfino")?;
523 let zapfino_regular = RunStyle {
524 font_id: fonts.select_font(&zapfino, &Properties::new())?,
525 color: Default::default(),
526 underline: Default::default(),
527 };
528 let menlo = fonts.load_family("Menlo")?;
529 let menlo_regular = RunStyle {
530 font_id: fonts.select_font(&menlo, &Properties::new())?,
531 color: Default::default(),
532 underline: Default::default(),
533 };
534
535 let text = "This is, mπre πr less, Zapfino!π";
536 let line = fonts.layout_line(
537 text,
538 16.0,
539 &[
540 (9, zapfino_regular),
541 (13, menlo_regular),
542 (text.len() - 22, zapfino_regular),
543 ],
544 );
545 assert_eq!(
546 line.runs
547 .iter()
548 .flat_map(|r| r.glyphs.iter())
549 .map(|g| g.index)
550 .collect::<Vec<_>>(),
551 vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
552 );
553 Ok(())
554 }
555
556 #[test]
557 #[ignore]
558 fn test_rasterize_glyph() {
559 use std::{fs::File, io::BufWriter, path::Path};
560
561 let fonts = FontSystem::new();
562 let font_ids = fonts.load_family("Fira Code").unwrap();
563 let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
564 let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
565
566 const VARIANTS: usize = 1;
567 for i in 0..VARIANTS {
568 let variant = i as f32 / VARIANTS as f32;
569 let (bounds, bytes) = fonts
570 .rasterize_glyph(
571 font_id,
572 16.0,
573 glyph_id,
574 vec2f(variant, variant),
575 2.,
576 RasterizationOptions::Alpha,
577 )
578 .unwrap();
579
580 let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
581 let path = Path::new(&name);
582 let file = File::create(path).unwrap();
583 let ref mut w = BufWriter::new(file);
584
585 let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
586 encoder.set_color(png::ColorType::Grayscale);
587 encoder.set_depth(png::BitDepth::Eight);
588 let mut writer = encoder.write_header().unwrap();
589 writer.write_image_data(&bytes).unwrap();
590 }
591 }
592
593 #[test]
594 fn test_wrap_line() {
595 let fonts = FontSystem::new();
596 let font_ids = fonts.load_family("Helvetica").unwrap();
597 let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
598
599 let line = "one two three four five\n";
600 let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
601 assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
602
603 let line = "aaa Ξ±Ξ±Ξ± βββ πππ\n";
604 let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
605 assert_eq!(
606 wrap_boundaries,
607 &["aaa Ξ±Ξ±Ξ± ".len(), "aaa Ξ±Ξ±Ξ± βββ ".len(),]
608 );
609 }
610
611 #[test]
612 fn test_layout_line_bom_char() {
613 let fonts = FontSystem::new();
614 let font_ids = fonts.load_family("Helvetica").unwrap();
615 let style = RunStyle {
616 font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
617 color: Default::default(),
618 underline: Default::default(),
619 };
620
621 let line = "\u{feff}";
622 let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
623 assert_eq!(layout.len, line.len());
624 assert!(layout.runs.is_empty());
625
626 let line = "a\u{feff}b";
627 let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
628 assert_eq!(layout.len, line.len());
629 assert_eq!(layout.runs.len(), 1);
630 assert_eq!(layout.runs[0].glyphs.len(), 2);
631 assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
632 // There's no glyph for \u{feff}
633 assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
634 }
635}