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