use anyhow::anyhow;use cocoa::appkit::CGFloat;use collections::{HashMap, HashSet};use core_foundation::{ array::{CFArray, CFArrayRef}, attributed_string::CFMutableAttributedString, base::{CFRange, CFType, TCFType}, number::CFNumber, string::CFString,};use core_graphics::{ base::{CGGlyph, kCGImageAlphaPremultipliedLast}, color_space::CGColorSpace, context::{CGContext, CGTextDrawingMode}, display::CGPoint,};use core_text::{ font::CTFont, font_collection::CTFontCollectionRef, font_descriptor::{ CTFontDescriptor, kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait, }, line::CTLine, string_attributes::kCTFontAttributeName,};use font_kit::{ font::Font as FontKitFont, handle::Handle, hinting::HintingOptions, metrics::Metrics, properties::{Style as FontkitStyle, Weight as FontkitWeight}, source::SystemSource, sources::mem::MemSource,};use gpui::{ Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, Hsla, LineLayout, Pixels, PlatformTextSystem, RenderGlyphParams, Result, Rgba, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, px, size, swap_rgba_pa_to_bgra,};use parking_lot::{RwLock, RwLockUpgradableReadGuard};use pathfinder_geometry::{ rect::{RectF, RectI}, transform2d::Transform2F, vector::Vector2F,};use smallvec::SmallVec;use std::{borrow::Cow, char, convert::TryFrom, sync::Arc, sync::OnceLock}; use crate::open_type::apply_features_and_fallbacks; #[allow(non_upper_case_globals)]const kCGImageAlphaOnly: u32 = 7; /// macOS text system using CoreText for font shaping.pub struct MacTextSystem(RwLock<MacTextSystemState>); #[derive(Clone, PartialEq, Eq, Hash)]struct FontKey { font_family: SharedString, font_features: FontFeatures, font_fallbacks: Option<FontFallbacks>,} struct MacTextSystemState { memory_source: MemSource, system_source: SystemSource, fonts: Vec<FontKitFont>, font_selections: HashMap<Font, FontId>, font_ids_by_postscript_name: HashMap<String, FontId>, font_ids_by_font_key: HashMap<FontKey, SmallVec<[FontId; 4]>>, postscript_names_by_font_id: HashMap<FontId, String>,} impl MacTextSystem { /// Create a new MacTextSystem. pub fn new() -> Self { Self(RwLock::new(MacTextSystemState { memory_source: MemSource::empty(), system_source: SystemSource::new(), fonts: Vec::new(), font_selections: HashMap::default(), font_ids_by_postscript_name: HashMap::default(), font_ids_by_font_key: HashMap::default(), postscript_names_by_font_id: HashMap::default(), })) }} impl Default for MacTextSystem { fn default() -> Self { Self::new() }} impl PlatformTextSystem for MacTextSystem { fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { self.0.write().add_fonts(fonts) } fn all_font_names(&self) -> Vec<String> { let mut names = Vec::new(); let collection = core_text::font_collection::create_for_all_families(); // NOTE: We intentionally avoid using `collection.get_descriptors()` here because // it has a memory leak bug in core-text v21.0.0. The upstream code uses // `wrap_under_get_rule` but `CTFontCollectionCreateMatchingFontDescriptors` // follows the Create Rule (caller owns the result), so it should use // `wrap_under_create_rule`. We call the function directly with correct memory management. unsafe extern "C" { fn CTFontCollectionCreateMatchingFontDescriptors( collection: CTFontCollectionRef, ) -> CFArrayRef; } let descriptors: Option<CFArray<CTFontDescriptor>> = unsafe { let array_ref = CTFontCollectionCreateMatchingFontDescriptors(collection.as_concrete_TypeRef()); if array_ref.is_null() { None } else { Some(CFArray::wrap_under_create_rule(array_ref)) } }; let Some(descriptors) = descriptors else { return names; }; for descriptor in descriptors.into_iter() { names.extend(lenient_font_attributes::family_name(&descriptor)); } if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() { names.extend(fonts_in_memory); } names } fn font_id(&self, font: &Font) -> Result<FontId> { let lock = self.0.upgradable_read(); if let Some(font_id) = lock.font_selections.get(font) { Ok(*font_id) } else { let mut lock = RwLockUpgradableReadGuard::upgrade(lock); let font_key = FontKey { font_family: font.family.clone(), font_features: font.features.clone(), font_fallbacks: font.fallbacks.clone(), }; let candidates = if let Some(font_ids) = lock.font_ids_by_font_key.get(&font_key) { font_ids.as_slice() } else { let font_ids = lock.load_family(&font.family, &font.features, font.fallbacks.as_ref())?; lock.font_ids_by_font_key.insert(font_key.clone(), font_ids); lock.font_ids_by_font_key[&font_key].as_ref() }; let candidate_properties = candidates .iter() .map(|font_id| lock.fonts[font_id.0].properties()) .collect::<SmallVec<[_; 4]>>(); let ix = font_kit::matching::find_best_match( &candidate_properties, &font_kit::properties::Properties { style: fontkit_style(font.style), weight: fontkit_weight(font.weight), stretch: Default::default(), }, )?; let font_id = candidates[ix]; lock.font_selections.insert(font.clone(), font_id); Ok(font_id) } } fn font_metrics(&self, font_id: FontId) -> FontMetrics { font_kit_metrics_to_metrics(self.0.read().fonts[font_id.0].metrics()) } fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> { Ok(bounds_from_rect( self.0.read().fonts[font_id.0].typographic_bounds(glyph_id.0)?, )) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { self.0.read().advance(font_id, glyph_id) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.0.read().glyph_for_char(font_id, ch) } fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { self.0.read().raster_bounds(params) } fn rasterize_glyph( &self, glyph_id: &RenderGlyphParams, raster_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { self.0.read().rasterize_glyph(glyph_id, raster_bounds) } fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, font_runs) } fn recommended_rendering_mode( &self, _font_id: FontId, _font_size: Pixels, ) -> TextRenderingMode { TextRenderingMode::Grayscale } fn glyph_dilation_for_color(&self, color: Hsla) -> u8 { // When font smoothing is enabled, CoreGraphics thickens glyph strokes by an amount that // depends on the foreground color's luminance. We replicate the logic used by CoreGraphics // to select between the different levels of dilation. if !font_smoothing_allowed_by_user() { return 0; } let rgba: Rgba = color.into(); let luminance = 0.2126 * rgba.r + 0.7152 * rgba.g + 0.0722 * rgba.b; let level = ((4.0 * luminance) + 0.5).floor() as i32; level.clamp(0, 4) as u8 }} fn font_smoothing_allowed_by_user() -> bool { static ALLOWED: OnceLock<bool> = OnceLock::new(); *ALLOWED.get_or_init(|| { use core_foundation_sys::preferences::{ CFPreferencesCopyAppValue, kCFPreferencesCurrentApplication, }; let key = CFString::new("AppleFontSmoothing"); let value_ref = unsafe { CFPreferencesCopyAppValue(key.as_concrete_TypeRef(), kCFPreferencesCurrentApplication) }; if value_ref.is_null() { return true; } let value = unsafe { CFType::wrap_under_create_rule(value_ref) }; let Some(number) = value.downcast_into::<CFNumber>() else { return true; }; // Only an explicit value of `0` means that font smoothing is disabled. number.to_i64() != Some(0) })} impl MacTextSystemState { fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { let fonts = fonts .into_iter() .map(|bytes| match bytes { Cow::Borrowed(embedded_font) => { let data_provider = unsafe { core_graphics::data_provider::CGDataProvider::from_slice(embedded_font) }; let font = core_graphics::font::CGFont::from_data_provider(data_provider) .map_err(|()| anyhow!("Could not load an embedded font."))?; let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font); Ok(Handle::from_native(&font)) } Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)), }) .collect::<Result<Vec<_>>>()?; self.memory_source.add_fonts(fonts.into_iter())?; Ok(()) } fn load_family( &mut self, name: &str, features: &FontFeatures, fallbacks: Option<&FontFallbacks>, ) -> Result<SmallVec<[FontId; 4]>> { let name = gpui::font_name_with_fallbacks(name, ".AppleSystemUIFont"); let mut font_ids = SmallVec::new(); let mut postscript_names_seen = HashSet::default(); let family = self .memory_source .select_family_by_name(name) .or_else(|_| self.system_source.select_family_by_name(name))?; for font in family.fonts() { let mut font = font.load()?; apply_features_and_fallbacks(&mut font, features, fallbacks)?; // This block contains a precautionary fix to guard against loading fonts // that might cause panics due to `.unwrap()`s up the chain. { // We use the 'm' character for text measurements in various spots // (e.g., the editor). However, at time of writing some of those usages // will panic if the font has no 'm' glyph. // // Therefore, we check up front that the font has the necessary glyph. let has_m_glyph = font.glyph_for_char('m').is_some(); // HACK: The 'Segoe Fluent Icons' font does not have an 'm' glyph, // but we need to be able to load it for rendering Windows icons in // the Storybook (on macOS). let is_segoe_fluent_icons = font.full_name() == "Segoe Fluent Icons"; if !has_m_glyph && !is_segoe_fluent_icons { // I spent far too long trying to track down why a font missing the 'm' // character wasn't loading. This log statement will hopefully save // someone else from suffering the same fate. log::warn!( "font '{}' has no 'm' character and was not loaded", font.full_name() ); continue; } } // We've seen a number of panics in production caused by calling font.properties() // which unwraps a downcast to CFNumber. This is an attempt to avoid the panic, // and to try and identify the incalcitrant font. let traits = font.native_font().all_traits(); if unsafe { !(traits .get(kCTFontSymbolicTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontWidthTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontWeightTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontSlantTrait) .downcast::<CFNumber>() .is_some()) } { log::error!( "Failed to read traits for font {:?} (PostScript name {:?})", font.full_name(), font.postscript_name(), ); continue; } let Some(postscript_name) = font.postscript_name() else { log::warn!( "font {:?} in family {:?} has no PostScript name; skipping", font.full_name(), name, ); continue; }; // Dedup is scoped to this single `load_family` call (issue #55472). // The same family can be reloaded later under a different `FontKey` // (different features/fallbacks); a global check against // `font_ids_by_postscript_name` would skip every already-registered // font and leave the second call's `font_ids` empty. if !postscript_names_seen.insert(postscript_name.clone()) { log::warn!( "skipping duplicate font {:?} with PostScript name {:?} \ in family {:?}", font.full_name(), postscript_name, name, ); continue; } let font_id = FontId(self.fonts.len()); font_ids.push(font_id); self.font_ids_by_postscript_name .insert(postscript_name.clone(), font_id); self.postscript_names_by_font_id .insert(font_id, postscript_name); self.fonts.push(font); } Ok(font_ids) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { Ok(size_from_vector2f( self.fonts[font_id.0].advance(glyph_id.0)?, )) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.fonts[font_id.0].glyph_for_char(ch).map(GlyphId) } fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId { let postscript_name = requested_font.postscript_name(); if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) { *font_id } else { let font_id = FontId(self.fonts.len()); self.font_ids_by_postscript_name .insert(postscript_name.clone(), font_id); self.postscript_names_by_font_id .insert(font_id, postscript_name); self.fonts .push(font_kit::font::Font::from_core_graphics_font( requested_font.copy_to_CGFont(), )); font_id } } fn is_emoji(&self, font_id: FontId) -> bool { self.postscript_names_by_font_id .get(&font_id) .is_some_and(|postscript_name| { postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI" }) } fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { let font = &self.fonts[params.font_id.0]; let scale = Transform2F::from_scale(params.scale_factor); let bounds: Bounds<DevicePixels> = bounds_from_rect_i(font.raster_bounds( params.glyph_id.0, params.font_size.into(), scale, HintingOptions::None, font_kit::canvas::RasterizationOptions::GrayscaleAa, )?); // Expand the bounds by 1 pixel on each side to give CG room for anti-aliasing. Ok(bounds.dilate(DevicePixels(1))) } fn rasterize_glyph( &self, params: &RenderGlyphParams, glyph_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { anyhow::bail!("glyph bounds are empty"); } else { // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing. let mut bitmap_size = glyph_bounds.size; if params.subpixel_variant.x > 0 { bitmap_size.width += DevicePixels(1); } if params.subpixel_variant.y > 0 { bitmap_size.height += DevicePixels(1); } let bitmap_size = bitmap_size; let mut bytes; let cx; if params.is_emoji { bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize]; cx = CGContext::create_bitmap_context( Some(bytes.as_mut_ptr() as *mut _), bitmap_size.width.0 as usize, bitmap_size.height.0 as usize, 8, bitmap_size.width.0 as usize * 4, &CGColorSpace::create_device_rgb(), kCGImageAlphaPremultipliedLast, ); } else { bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize]; cx = CGContext::create_bitmap_context( Some(bytes.as_mut_ptr() as *mut _), bitmap_size.width.0 as usize, bitmap_size.height.0 as usize, 8, bitmap_size.width.0 as usize, &CGColorSpace::create_device_gray(), kCGImageAlphaOnly, ); } // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. cx.translate( -glyph_bounds.origin.x.0 as CGFloat, (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat, ); cx.scale( params.scale_factor as CGFloat, params.scale_factor as CGFloat, ); let subpixel_shift = params .subpixel_variant .map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32); cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); cx.set_allows_antialiasing(true); cx.set_should_antialias(true); cx.set_allows_font_subpixel_positioning(true); cx.set_should_subpixel_position_fonts(true); cx.set_allows_font_subpixel_quantization(false); cx.set_should_subpixel_quantize_fonts(false);Lines 1–500 of 904. Load more
use anyhow::anyhow; use cocoa::appkit::CGFloat; use collections::{HashMap, HashSet}; use core_foundation::{ array::{CFArray, CFArrayRef}, attributed_string::CFMutableAttributedString, base::{CFRange, CFType, TCFType}, number::CFNumber, string::CFString, }; use core_graphics::{ base::{CGGlyph, kCGImageAlphaPremultipliedLast}, color_space::CGColorSpace, context::{CGContext, CGTextDrawingMode}, display::CGPoint, }; use core_text::{ font::CTFont, font_collection::CTFontCollectionRef, font_descriptor::{ CTFontDescriptor, kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait, }, line::CTLine, string_attributes::kCTFontAttributeName, }; use font_kit::{ font::Font as FontKitFont, handle::Handle, hinting::HintingOptions, metrics::Metrics, properties::{Style as FontkitStyle, Weight as FontkitWeight}, source::SystemSource, sources::mem::MemSource, }; use gpui::{ Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, Hsla, LineLayout, Pixels, PlatformTextSystem, RenderGlyphParams, Result, Rgba, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, px, size, swap_rgba_pa_to_bgra, }; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use pathfinder_geometry::{ rect::{RectF, RectI}, transform2d::Transform2F, vector::Vector2F, }; use smallvec::SmallVec; use std::{borrow::Cow, char, convert::TryFrom, sync::Arc, sync::OnceLock}; use crate::open_type::apply_features_and_fallbacks; #[allow(non_upper_case_globals)] const kCGImageAlphaOnly: u32 = 7; /// macOS text system using CoreText for font shaping. pub struct MacTextSystem(RwLock<MacTextSystemState>); #[derive(Clone, PartialEq, Eq, Hash)] struct FontKey { font_family: SharedString, font_features: FontFeatures, font_fallbacks: Option<FontFallbacks>, } struct MacTextSystemState { memory_source: MemSource, system_source: SystemSource, fonts: Vec<FontKitFont>, font_selections: HashMap<Font, FontId>, font_ids_by_postscript_name: HashMap<String, FontId>, font_ids_by_font_key: HashMap<FontKey, SmallVec<[FontId; 4]>>, postscript_names_by_font_id: HashMap<FontId, String>, } impl MacTextSystem { /// Create a new MacTextSystem. pub fn new() -> Self { Self(RwLock::new(MacTextSystemState { memory_source: MemSource::empty(), system_source: SystemSource::new(), fonts: Vec::new(), font_selections: HashMap::default(), font_ids_by_postscript_name: HashMap::default(), font_ids_by_font_key: HashMap::default(), postscript_names_by_font_id: HashMap::default(), })) } } impl Default for MacTextSystem { fn default() -> Self { Self::new() } } impl PlatformTextSystem for MacTextSystem { fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { self.0.write().add_fonts(fonts) } fn all_font_names(&self) -> Vec<String> { let mut names = Vec::new(); let collection = core_text::font_collection::create_for_all_families(); // NOTE: We intentionally avoid using `collection.get_descriptors()` here because // it has a memory leak bug in core-text v21.0.0. The upstream code uses // `wrap_under_get_rule` but `CTFontCollectionCreateMatchingFontDescriptors` // follows the Create Rule (caller owns the result), so it should use // `wrap_under_create_rule`. We call the function directly with correct memory management. unsafe extern "C" { fn CTFontCollectionCreateMatchingFontDescriptors( collection: CTFontCollectionRef, ) -> CFArrayRef; } let descriptors: Option<CFArray<CTFontDescriptor>> = unsafe { let array_ref = CTFontCollectionCreateMatchingFontDescriptors(collection.as_concrete_TypeRef()); if array_ref.is_null() { None } else { Some(CFArray::wrap_under_create_rule(array_ref)) } }; let Some(descriptors) = descriptors else { return names; }; for descriptor in descriptors.into_iter() { names.extend(lenient_font_attributes::family_name(&descriptor)); } if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() { names.extend(fonts_in_memory); } names } fn font_id(&self, font: &Font) -> Result<FontId> { let lock = self.0.upgradable_read(); if let Some(font_id) = lock.font_selections.get(font) { Ok(*font_id) } else { let mut lock = RwLockUpgradableReadGuard::upgrade(lock); let font_key = FontKey { font_family: font.family.clone(), font_features: font.features.clone(), font_fallbacks: font.fallbacks.clone(), }; let candidates = if let Some(font_ids) = lock.font_ids_by_font_key.get(&font_key) { font_ids.as_slice() } else { let font_ids = lock.load_family(&font.family, &font.features, font.fallbacks.as_ref())?; lock.font_ids_by_font_key.insert(font_key.clone(), font_ids); lock.font_ids_by_font_key[&font_key].as_ref() }; let candidate_properties = candidates .iter() .map(|font_id| lock.fonts[font_id.0].properties()) .collect::<SmallVec<[_; 4]>>(); let ix = font_kit::matching::find_best_match( &candidate_properties, &font_kit::properties::Properties { style: fontkit_style(font.style), weight: fontkit_weight(font.weight), stretch: Default::default(), }, )?; let font_id = candidates[ix]; lock.font_selections.insert(font.clone(), font_id); Ok(font_id) } } fn font_metrics(&self, font_id: FontId) -> FontMetrics { font_kit_metrics_to_metrics(self.0.read().fonts[font_id.0].metrics()) } fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> { Ok(bounds_from_rect( self.0.read().fonts[font_id.0].typographic_bounds(glyph_id.0)?, )) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { self.0.read().advance(font_id, glyph_id) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.0.read().glyph_for_char(font_id, ch) } fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { self.0.read().raster_bounds(params) } fn rasterize_glyph( &self, glyph_id: &RenderGlyphParams, raster_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { self.0.read().rasterize_glyph(glyph_id, raster_bounds) } fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, font_runs) } fn recommended_rendering_mode( &self, _font_id: FontId, _font_size: Pixels, ) -> TextRenderingMode { TextRenderingMode::Grayscale } fn glyph_dilation_for_color(&self, color: Hsla) -> u8 { // When font smoothing is enabled, CoreGraphics thickens glyph strokes by an amount that // depends on the foreground color's luminance. We replicate the logic used by CoreGraphics // to select between the different levels of dilation. if !font_smoothing_allowed_by_user() { return 0; } let rgba: Rgba = color.into(); let luminance = 0.2126 * rgba.r + 0.7152 * rgba.g + 0.0722 * rgba.b; let level = ((4.0 * luminance) + 0.5).floor() as i32; level.clamp(0, 4) as u8 } } fn font_smoothing_allowed_by_user() -> bool { static ALLOWED: OnceLock<bool> = OnceLock::new(); *ALLOWED.get_or_init(|| { use core_foundation_sys::preferences::{ CFPreferencesCopyAppValue, kCFPreferencesCurrentApplication, }; let key = CFString::new("AppleFontSmoothing"); let value_ref = unsafe { CFPreferencesCopyAppValue(key.as_concrete_TypeRef(), kCFPreferencesCurrentApplication) }; if value_ref.is_null() { return true; } let value = unsafe { CFType::wrap_under_create_rule(value_ref) }; let Some(number) = value.downcast_into::<CFNumber>() else { return true; }; // Only an explicit value of `0` means that font smoothing is disabled. number.to_i64() != Some(0) }) } impl MacTextSystemState { fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { let fonts = fonts .into_iter() .map(|bytes| match bytes { Cow::Borrowed(embedded_font) => { let data_provider = unsafe { core_graphics::data_provider::CGDataProvider::from_slice(embedded_font) }; let font = core_graphics::font::CGFont::from_data_provider(data_provider) .map_err(|()| anyhow!("Could not load an embedded font."))?; let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font); Ok(Handle::from_native(&font)) } Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)), }) .collect::<Result<Vec<_>>>()?; self.memory_source.add_fonts(fonts.into_iter())?; Ok(()) } fn load_family( &mut self, name: &str, features: &FontFeatures, fallbacks: Option<&FontFallbacks>, ) -> Result<SmallVec<[FontId; 4]>> { let name = gpui::font_name_with_fallbacks(name, ".AppleSystemUIFont"); let mut font_ids = SmallVec::new(); let mut postscript_names_seen = HashSet::default(); let family = self .memory_source .select_family_by_name(name) .or_else(|_| self.system_source.select_family_by_name(name))?; for font in family.fonts() { let mut font = font.load()?; apply_features_and_fallbacks(&mut font, features, fallbacks)?; // This block contains a precautionary fix to guard against loading fonts // that might cause panics due to `.unwrap()`s up the chain. { // We use the 'm' character for text measurements in various spots // (e.g., the editor). However, at time of writing some of those usages // will panic if the font has no 'm' glyph. // // Therefore, we check up front that the font has the necessary glyph. let has_m_glyph = font.glyph_for_char('m').is_some(); // HACK: The 'Segoe Fluent Icons' font does not have an 'm' glyph, // but we need to be able to load it for rendering Windows icons in // the Storybook (on macOS). let is_segoe_fluent_icons = font.full_name() == "Segoe Fluent Icons"; if !has_m_glyph && !is_segoe_fluent_icons { // I spent far too long trying to track down why a font missing the 'm' // character wasn't loading. This log statement will hopefully save // someone else from suffering the same fate. log::warn!( "font '{}' has no 'm' character and was not loaded", font.full_name() ); continue; } } // We've seen a number of panics in production caused by calling font.properties() // which unwraps a downcast to CFNumber. This is an attempt to avoid the panic, // and to try and identify the incalcitrant font. let traits = font.native_font().all_traits(); if unsafe { !(traits .get(kCTFontSymbolicTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontWidthTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontWeightTrait) .downcast::<CFNumber>() .is_some() && traits .get(kCTFontSlantTrait) .downcast::<CFNumber>() .is_some()) } { log::error!( "Failed to read traits for font {:?} (PostScript name {:?})", font.full_name(), font.postscript_name(), ); continue; } let Some(postscript_name) = font.postscript_name() else { log::warn!( "font {:?} in family {:?} has no PostScript name; skipping", font.full_name(), name, ); continue; }; // Dedup is scoped to this single `load_family` call (issue #55472). // The same family can be reloaded later under a different `FontKey` // (different features/fallbacks); a global check against // `font_ids_by_postscript_name` would skip every already-registered // font and leave the second call's `font_ids` empty. if !postscript_names_seen.insert(postscript_name.clone()) { log::warn!( "skipping duplicate font {:?} with PostScript name {:?} \ in family {:?}", font.full_name(), postscript_name, name, ); continue; } let font_id = FontId(self.fonts.len()); font_ids.push(font_id); self.font_ids_by_postscript_name .insert(postscript_name.clone(), font_id); self.postscript_names_by_font_id .insert(font_id, postscript_name); self.fonts.push(font); } Ok(font_ids) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { Ok(size_from_vector2f( self.fonts[font_id.0].advance(glyph_id.0)?, )) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.fonts[font_id.0].glyph_for_char(ch).map(GlyphId) } fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId { let postscript_name = requested_font.postscript_name(); if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) { *font_id } else { let font_id = FontId(self.fonts.len()); self.font_ids_by_postscript_name .insert(postscript_name.clone(), font_id); self.postscript_names_by_font_id .insert(font_id, postscript_name); self.fonts .push(font_kit::font::Font::from_core_graphics_font( requested_font.copy_to_CGFont(), )); font_id } } fn is_emoji(&self, font_id: FontId) -> bool { self.postscript_names_by_font_id .get(&font_id) .is_some_and(|postscript_name| { postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI" }) } fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { let font = &self.fonts[params.font_id.0]; let scale = Transform2F::from_scale(params.scale_factor); let bounds: Bounds<DevicePixels> = bounds_from_rect_i(font.raster_bounds( params.glyph_id.0, params.font_size.into(), scale, HintingOptions::None, font_kit::canvas::RasterizationOptions::GrayscaleAa, )?); // Expand the bounds by 1 pixel on each side to give CG room for anti-aliasing. Ok(bounds.dilate(DevicePixels(1))) } fn rasterize_glyph( &self, params: &RenderGlyphParams, glyph_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { anyhow::bail!("glyph bounds are empty"); } else { // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing. let mut bitmap_size = glyph_bounds.size; if params.subpixel_variant.x > 0 { bitmap_size.width += DevicePixels(1); } if params.subpixel_variant.y > 0 { bitmap_size.height += DevicePixels(1); } let bitmap_size = bitmap_size; let mut bytes; let cx; if params.is_emoji { bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize]; cx = CGContext::create_bitmap_context( Some(bytes.as_mut_ptr() as *mut _), bitmap_size.width.0 as usize, bitmap_size.height.0 as usize, 8, bitmap_size.width.0 as usize * 4, &CGColorSpace::create_device_rgb(), kCGImageAlphaPremultipliedLast, ); } else { bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize]; cx = CGContext::create_bitmap_context( Some(bytes.as_mut_ptr() as *mut _), bitmap_size.width.0 as usize, bitmap_size.height.0 as usize, 8, bitmap_size.width.0 as usize, &CGColorSpace::create_device_gray(), kCGImageAlphaOnly, ); } // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. cx.translate( -glyph_bounds.origin.x.0 as CGFloat, (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat, ); cx.scale( params.scale_factor as CGFloat, params.scale_factor as CGFloat, ); let subpixel_shift = params .subpixel_variant .map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32); cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); cx.set_allows_antialiasing(true); cx.set_should_antialias(true); cx.set_allows_font_subpixel_positioning(true); cx.set_should_subpixel_position_fonts(true); cx.set_allows_font_subpixel_quantization(false); cx.set_should_subpixel_quantize_fonts(false);