Checkpoint

Nathan Sobo created

Change summary

Cargo.lock                                    |   1 
crates/gpui3/Cargo.toml                       |   1 
crates/gpui3/src/app.rs                       |   5 
crates/gpui3/src/gpui3.rs                     |   2 
crates/gpui3/src/platform.rs                  |  44 +++-
crates/gpui3/src/platform/mac/open_type.rs    |  73 ++++----
crates/gpui3/src/platform/mac/screen.rs       |   4 
crates/gpui3/src/platform/mac/text_system.rs  | 150 ++++++++++++-------
crates/gpui3/src/text_system.rs               | 113 ++++++--------
crates/gpui3/src/text_system/font_features.rs | 162 +++++++++++++++++++++
crates/gpui3/src/text_system/line_wrapper.rs  |   4 
11 files changed, 377 insertions(+), 182 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3243,6 +3243,7 @@ dependencies = [
  "async-task",
  "backtrace",
  "bindgen 0.65.1",
+ "bitflags 2.4.0",
  "block",
  "bytemuck",
  "cbindgen",

crates/gpui3/Cargo.toml 🔗

@@ -58,6 +58,7 @@ slotmap = "1.0.6"
 bytemuck = { version = "1.14.0", features = ["derive"] }
 schemars.workspace = true
 plane-split = "0.18.0"
+bitflags = "2.4.0"
 
 [dev-dependencies]
 backtrace = "0.3"

crates/gpui3/src/app.rs 🔗

@@ -13,6 +13,7 @@ use std::{
     marker::PhantomData,
     sync::{Arc, Weak},
 };
+use util::ResultExt;
 
 #[derive(Clone)]
 pub struct App(Arc<Mutex<AppContext>>);
@@ -173,7 +174,9 @@ impl AppContext {
             .collect::<Vec<_>>();
 
         for dirty_window_id in dirty_window_ids {
-            self.update_window(dirty_window_id, |cx| cx.draw());
+            self.update_window(dirty_window_id, |cx| cx.draw())
+                .unwrap() // We know we have the window.
+                .log_err();
         }
     }
 

crates/gpui3/src/gpui3.rs 🔗

@@ -77,7 +77,7 @@ impl<T> Flatten<T> for Result<T> {
     }
 }
 
-#[derive(Clone, Eq, PartialEq)]
+#[derive(Clone, Eq, PartialEq, Hash)]
 pub struct SharedString(ArcCow<'static, str>);
 
 impl Default for SharedString {

crates/gpui3/src/platform.rs 🔗

@@ -6,8 +6,8 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
-    LineLayout, Pixels, Point, Result, RunStyle, Scene, SharedString, Size,
+    AnyWindowHandle, Bounds, FontFeatures, FontId, FontStyle, FontWeight, GlyphId, LineLayout,
+    Pixels, Point, Result, RunStyle, Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -25,7 +25,6 @@ use std::{
     str::FromStr,
     sync::Arc,
 };
-pub use time::UtcOffset;
 use uuid::Uuid;
 
 pub use events::*;
@@ -34,6 +33,7 @@ pub use keystroke::*;
 pub use mac::*;
 #[cfg(any(test, feature = "test"))]
 pub use test::*;
+pub use time::UtcOffset;
 
 #[cfg(target_os = "macos")]
 pub(crate) fn current_platform() -> Arc<dyn Platform> {
@@ -154,19 +154,12 @@ pub trait PlatformDispatcher: Send + Sync {
 }
 
 pub trait PlatformTextSystem: Send + Sync {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
-    fn all_families(&self) -> Vec<String>;
-    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
-    fn select_font(
-        &self,
-        font_ids: &[FontId],
-        weight: FontWeight,
-        style: FontStyle,
-    ) -> anyhow::Result<FontId>;
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
+    fn all_font_families(&self) -> Vec<String>;
+    fn select_font(&self, descriptor: FontDescriptor) -> Result<FontId>;
     fn font_metrics(&self, font_id: FontId) -> FontMetrics;
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId)
-        -> anyhow::Result<Bounds<f32>>;
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>>;
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn rasterize_glyph(
         &self,
@@ -404,3 +397,24 @@ impl ClipboardItem {
         hasher.finish()
     }
 }
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct FontDescriptor {
+    family: SharedString,
+    features: FontFeatures,
+    weight: FontWeight,
+    style: FontStyle,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct FontMetrics {
+    pub units_per_em: u32,
+    pub ascent: f32,
+    pub descent: f32,
+    pub line_gap: f32,
+    pub underline_position: f32,
+    pub underline_thickness: f32,
+    pub cap_height: f32,
+    pub x_height: f32,
+    pub bounding_box: Bounds<f32>,
+}

crates/gpui3/src/platform/mac/open_type.rs 🔗

@@ -1,7 +1,5 @@
 #![allow(unused, non_upper_case_globals)]
 
-use std::ptr;
-
 use crate::FontFeatures;
 use cocoa::appkit::CGFloat;
 use core_foundation::{base::TCFType, number::CFNumber};
@@ -13,6 +11,7 @@ use core_text::{
     },
 };
 use font_kit::font::Font;
+use std::ptr;
 
 const kCaseSensitiveLayoutOffSelector: i32 = 1;
 const kCaseSensitiveLayoutOnSelector: i32 = 0;
@@ -108,243 +107,243 @@ const kTypographicExtrasType: i32 = 14;
 const kVerticalFractionsSelector: i32 = 1;
 const kVerticalPositionType: i32 = 10;
 
-pub fn apply_features(font: &mut Font, features: &FontFeatures) {
+pub fn apply_features(font: &mut Font, features: FontFeatures) {
     // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
     // for a reference implementation.
     toggle_open_type_feature(
         font,
-        features.calt,
+        features.calt(),
         kContextualAlternatesType,
         kContextualAlternatesOnSelector,
         kContextualAlternatesOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.case,
+        features.case(),
         kCaseSensitiveLayoutType,
         kCaseSensitiveLayoutOnSelector,
         kCaseSensitiveLayoutOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.cpsp,
+        features.cpsp(),
         kCaseSensitiveLayoutType,
         kCaseSensitiveSpacingOnSelector,
         kCaseSensitiveSpacingOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.frac,
+        features.frac(),
         kFractionsType,
         kDiagonalFractionsSelector,
         kNoFractionsSelector,
     );
     toggle_open_type_feature(
         font,
-        features.liga,
+        features.liga(),
         kLigaturesType,
         kCommonLigaturesOnSelector,
         kCommonLigaturesOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.onum,
+        features.onum(),
         kNumberCaseType,
         kLowerCaseNumbersSelector,
         2,
     );
     toggle_open_type_feature(
         font,
-        features.ordn,
+        features.ordn(),
         kVerticalPositionType,
         kOrdinalsSelector,
         kNormalPositionSelector,
     );
     toggle_open_type_feature(
         font,
-        features.pnum,
+        features.pnum(),
         kNumberSpacingType,
         kProportionalNumbersSelector,
         4,
     );
     toggle_open_type_feature(
         font,
-        features.ss01,
+        features.ss01(),
         kStylisticAlternativesType,
         kStylisticAltOneOnSelector,
         kStylisticAltOneOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss02,
+        features.ss02(),
         kStylisticAlternativesType,
         kStylisticAltTwoOnSelector,
         kStylisticAltTwoOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss03,
+        features.ss03(),
         kStylisticAlternativesType,
         kStylisticAltThreeOnSelector,
         kStylisticAltThreeOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss04,
+        features.ss04(),
         kStylisticAlternativesType,
         kStylisticAltFourOnSelector,
         kStylisticAltFourOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss05,
+        features.ss05(),
         kStylisticAlternativesType,
         kStylisticAltFiveOnSelector,
         kStylisticAltFiveOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss06,
+        features.ss06(),
         kStylisticAlternativesType,
         kStylisticAltSixOnSelector,
         kStylisticAltSixOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss07,
+        features.ss07(),
         kStylisticAlternativesType,
         kStylisticAltSevenOnSelector,
         kStylisticAltSevenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss08,
+        features.ss08(),
         kStylisticAlternativesType,
         kStylisticAltEightOnSelector,
         kStylisticAltEightOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss09,
+        features.ss09(),
         kStylisticAlternativesType,
         kStylisticAltNineOnSelector,
         kStylisticAltNineOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss10,
+        features.ss10(),
         kStylisticAlternativesType,
         kStylisticAltTenOnSelector,
         kStylisticAltTenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss11,
+        features.ss11(),
         kStylisticAlternativesType,
         kStylisticAltElevenOnSelector,
         kStylisticAltElevenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss12,
+        features.ss12(),
         kStylisticAlternativesType,
         kStylisticAltTwelveOnSelector,
         kStylisticAltTwelveOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss13,
+        features.ss13(),
         kStylisticAlternativesType,
         kStylisticAltThirteenOnSelector,
         kStylisticAltThirteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss14,
+        features.ss14(),
         kStylisticAlternativesType,
         kStylisticAltFourteenOnSelector,
         kStylisticAltFourteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss15,
+        features.ss15(),
         kStylisticAlternativesType,
         kStylisticAltFifteenOnSelector,
         kStylisticAltFifteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss16,
+        features.ss16(),
         kStylisticAlternativesType,
         kStylisticAltSixteenOnSelector,
         kStylisticAltSixteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss17,
+        features.ss17(),
         kStylisticAlternativesType,
         kStylisticAltSeventeenOnSelector,
         kStylisticAltSeventeenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss18,
+        features.ss18(),
         kStylisticAlternativesType,
         kStylisticAltEighteenOnSelector,
         kStylisticAltEighteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss19,
+        features.ss19(),
         kStylisticAlternativesType,
         kStylisticAltNineteenOnSelector,
         kStylisticAltNineteenOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.ss20,
+        features.ss20(),
         kStylisticAlternativesType,
         kStylisticAltTwentyOnSelector,
         kStylisticAltTwentyOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.subs,
+        features.subs(),
         kVerticalPositionType,
         kInferiorsSelector,
         kNormalPositionSelector,
     );
     toggle_open_type_feature(
         font,
-        features.sups,
+        features.sups(),
         kVerticalPositionType,
         kSuperiorsSelector,
         kNormalPositionSelector,
     );
     toggle_open_type_feature(
         font,
-        features.swsh,
+        features.swsh(),
         kContextualAlternatesType,
         kSwashAlternatesOnSelector,
         kSwashAlternatesOffSelector,
     );
     toggle_open_type_feature(
         font,
-        features.titl,
+        features.titl(),
         kStyleOptionsType,
         kTitlingCapsSelector,
         kNoStyleOptionsSelector,
     );
     toggle_open_type_feature(
         font,
-        features.tnum,
+        features.tnum(),
         kNumberSpacingType,
         kMonospacedNumbersSelector,
         4,
     );
     toggle_open_type_feature(
         font,
-        features.zero,
+        features.zero(),
         kTypographicExtrasType,
         kSlashedZeroOnSelector,
         kSlashedZeroOffSelector,

crates/gpui3/src/platform/mac/screen.rs 🔗

@@ -1,7 +1,5 @@
 use super::ns_string;
-use crate::{
-    platform, point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId,
-};
+use crate::{point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId};
 use cocoa::{
     appkit::NSScreen,
     base::{id, nil},

crates/gpui3/src/platform/mac/text_system.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, size, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
-    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Run, RunStyle,
-    Size,
+    platform::FontDescriptor, point, px, size, Bounds, FontFeatures, FontId, FontMetrics,
+    FontStyle, FontWeight, Glyph, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
+    RasterizationOptions, Result, Run, RunStyle, SharedString, Size,
 };
 use cocoa::appkit::{CGFloat, CGPoint};
 use collections::HashMap;
@@ -31,6 +31,7 @@ use pathfinder_geometry::{
     transform2d::Transform2F,
     vector::{Vector2F, Vector2I},
 };
+use smallvec::SmallVec;
 use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
 
 use super::open_type;
@@ -44,7 +45,9 @@ struct TextSystemState {
     memory_source: MemSource,
     system_source: SystemSource,
     fonts: Vec<font_kit::font::Font>,
+    font_selections: HashMap<FontDescriptor, FontId>,
     font_ids_by_postscript_name: HashMap<String, FontId>,
+    font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
     postscript_names_by_font_id: HashMap<FontId, String>,
 }
 
@@ -54,8 +57,10 @@ impl MacTextSystem {
             memory_source: MemSource::empty(),
             system_source: SystemSource::new(),
             fonts: Vec::new(),
-            font_ids_by_postscript_name: Default::default(),
-            postscript_names_by_font_id: Default::default(),
+            font_selections: HashMap::default(),
+            font_ids_by_postscript_name: HashMap::default(),
+            font_ids_by_family_name: HashMap::default(),
+            postscript_names_by_font_id: HashMap::default(),
         }))
     }
 }
@@ -67,11 +72,11 @@ impl Default for MacTextSystem {
 }
 
 impl PlatformTextSystem for MacTextSystem {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
         self.0.write().add_fonts(fonts)
     }
 
-    fn all_families(&self) -> Vec<String> {
+    fn all_font_families(&self) -> Vec<String> {
         self.0
             .read()
             .system_source
@@ -79,32 +84,49 @@ impl PlatformTextSystem for MacTextSystem {
             .expect("core text should never return an error")
     }
 
-    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
-        self.0.write().load_family(name, features)
-    }
-
-    fn select_font(
-        &self,
-        font_ids: &[FontId],
-        weight: FontWeight,
-        style: FontStyle,
-    ) -> anyhow::Result<FontId> {
-        self.0.read().select_font(font_ids, weight, style)
+    fn select_font(&self, descriptor: FontDescriptor) -> Result<FontId> {
+        let lock = self.0.upgradable_read();
+        if let Some(font_id) = lock.font_selections.get(&descriptor) {
+            Ok(*font_id)
+        } else {
+            let mut lock = parking_lot::RwLockUpgradableReadGuard::upgrade(lock);
+            let candidates =
+                if let Some(font_ids) = lock.font_ids_by_family_name.get(&descriptor.family) {
+                    font_ids.as_slice()
+                } else {
+                    let font_ids = lock.load_family(&descriptor.family, descriptor.features)?;
+                    lock.font_ids_by_family_name
+                        .insert(descriptor.family.clone(), font_ids);
+                    lock.font_ids_by_family_name[&descriptor.family].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: descriptor.style.into(),
+                    weight: descriptor.weight.into(),
+                    stretch: Default::default(),
+                },
+            )?;
+
+            Ok(candidates[ix])
+        }
     }
 
     fn font_metrics(&self, font_id: FontId) -> FontMetrics {
         self.0.read().font_metrics(font_id)
     }
 
-    fn typographic_bounds(
-        &self,
-        font_id: FontId,
-        glyph_id: GlyphId,
-    ) -> anyhow::Result<Bounds<f32>> {
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
         self.0.read().typographic_bounds(font_id, glyph_id)
     }
 
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
         self.0.read().advance(font_id, glyph_id)
     }
 
@@ -147,7 +169,7 @@ impl PlatformTextSystem for MacTextSystem {
 }
 
 impl TextSystemState {
-    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
+    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
         self.memory_source.add_fonts(
             fonts
                 .iter()
@@ -156,13 +178,16 @@ impl TextSystemState {
         Ok(())
     }
 
-    fn load_family(&mut self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
-        let mut font_ids = Vec::new();
-
+    fn load_family(
+        &mut self,
+        name: &SharedString,
+        features: FontFeatures,
+    ) -> Result<SmallVec<[FontId; 4]>> {
+        let mut font_ids = SmallVec::new();
         let family = self
             .memory_source
-            .select_family_by_name(name)
-            .or_else(|_| self.system_source.select_family_by_name(name))?;
+            .select_family_by_name(name.as_ref())
+            .or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
         for font in family.fonts() {
             let mut font = font.load()?;
             open_type::apply_features(&mut font, features);
@@ -178,42 +203,53 @@ impl TextSystemState {
         Ok(font_ids)
     }
 
-    fn select_font(
-        &self,
-        font_ids: &[FontId],
-        weight: FontWeight,
-        style: FontStyle,
-    ) -> anyhow::Result<FontId> {
-        let candidates = font_ids
-            .iter()
-            .map(|font_id| self.fonts[font_id.0].properties())
-            .collect::<Vec<_>>();
-        let idx = font_kit::matching::find_best_match(
-            &candidates,
-            &font_kit::properties::Properties {
-                style: style.into(),
-                weight: weight.into(),
-                stretch: Default::default(),
-            },
-        )?;
-        Ok(font_ids[idx])
-    }
+    // fn select_font(
+    //     &mut self,
+    //     family: &SharedString,
+    //     weight: FontWeight,
+    //     style: FontStyle,
+    //     features: FontFeatures,
+    // ) -> Result<FontId> {
+    //     let candidates = if let Some(font_ids) = self.font_ids_by_family_name.get(family) {
+    //         font_ids
+    //     } else {
+    //         let font_ids = if let Some(font_ids) = self.font_ids_by_family_name.get(family) {
+    //             font_ids.as_slice()
+    //         } else {
+    //             self.font_ids_by_family_name
+    //                 .insert(family.clone())
+    //                 .or_insert(font_ids).as_slice()
+    //         };
+
+    //     };
+
+    //     let font_properties = candidates
+    //         .iter()
+    //         .map(|font_id| self.fonts[font_id.0].properties())
+    //         .collect::<SmallVec<[_; 4]>>();
+
+    //     // let idx = font_kit::matching::find_best_match(
+    //     //     &candidates,
+    //     //     &font_kit::properties::Properties {
+    //     //         style: style.into(),
+    //     //         weight: weight.into(),
+    //     //         stretch: Default::default(),
+    //     //     },
+    //     // )?;
+    //     // Ok(font_ids[idx])
+    // }
 
     fn font_metrics(&self, font_id: FontId) -> FontMetrics {
         self.fonts[font_id.0].metrics().into()
     }
 
-    fn typographic_bounds(
-        &self,
-        font_id: FontId,
-        glyph_id: GlyphId,
-    ) -> anyhow::Result<Bounds<f32>> {
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
         Ok(self.fonts[font_id.0]
             .typographic_bounds(glyph_id.into())?
             .into())
     }
 
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
         Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
     }
 
@@ -659,7 +695,7 @@ impl From<FontStyle> for FontkitStyle {
 //     }
 
 //     #[test]
-//     fn test_glyph_offsets() -> anyhow::Result<()> {
+//     fn test_glyph_offsets() -> crate::Result<()> {
 //         let fonts = FontSystem::new();
 //         let zapfino = fonts.load_family("Zapfino", &Default::default())?;
 //         let zapfino_regular = RunStyle {

crates/gpui3/src/text_system.rs 🔗

@@ -1,11 +1,9 @@
-mod font_cache;
+mod font_features;
 mod line_wrapper;
 mod text_layout_cache;
 
-pub use font_cache::*;
+pub use font_features::*;
 use line_wrapper::*;
-use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
 pub use text_layout_cache::*;
 
 use crate::{Hsla, Pixels, PlatformTextSystem, Point, Result, Size, UnderlineStyle};
@@ -19,8 +17,13 @@ use std::{
     sync::Arc,
 };
 
+#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
+pub struct FontId(pub usize);
+
+#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
+pub struct FontFamilyId(pub usize);
+
 pub struct TextSystem {
-    font_cache: Arc<FontCache>,
     text_layout_cache: Arc<TextLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
     wrapper_pool: Mutex<HashMap<(FontId, Pixels), Vec<LineWrapper>>>,
@@ -29,7 +32,7 @@ pub struct TextSystem {
 impl TextSystem {
     pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
         TextSystem {
-            font_cache: Arc::new(FontCache::new(platform_text_system.clone())),
+            // font_cache: Arc::new(FontCache::new(platform_text_system.clone())),
             text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
             platform_text_system,
             wrapper_pool: Mutex::new(HashMap::default()),
@@ -37,7 +40,8 @@ impl TextSystem {
     }
 
     pub fn font_family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
-        self.font_cache.family_name(family_id)
+        todo!()
+        // self.font_cache.family_name(family_id)
     }
 
     pub fn load_font_family(
@@ -45,16 +49,19 @@ impl TextSystem {
         names: &[&str],
         features: &FontFeatures,
     ) -> Result<FontFamilyId> {
-        self.font_cache.load_family(names, features)
+        todo!()
+        // self.font_cache.load_family(names, features)
     }
 
     /// Returns an arbitrary font family that is available on the system.
     pub fn known_existing_font_family(&self) -> FontFamilyId {
-        self.font_cache.known_existing_family()
+        todo!()
+        // self.font_cache.known_existing_family()
     }
 
     pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
-        self.font_cache.default_font(family_id)
+        todo!()
+        // self.font_cache.default_font(family_id)
     }
 
     pub fn select_font(
@@ -63,55 +70,67 @@ impl TextSystem {
         weight: FontWeight,
         style: FontStyle,
     ) -> Result<FontId> {
-        self.font_cache.select_font(family_id, weight, style)
+        todo!()
+        // self.font_cache.select_font(family_id, weight, style)
     }
 
-    pub fn read_font_metric<F, T>(&self, font_id: FontId, f: F) -> T
-    where
-        F: FnOnce(&FontMetrics) -> T,
-        T: 'static,
-    {
-        self.font_cache.read_metric(font_id, f)
-    }
+    // pub fn read_font_metric<F, T>(&self, font_id: FontId, f: F) -> T
+    // where
+    //     F: FnOnce(&FontMetrics) -> T,
+    //     T: 'static,
+    // {
+    //     todo!()
+    //     // self.font_cache.read_metric(font_id, f)
+    // }
 
     pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
-        self.font_cache.bounding_box(font_id, font_size)
+        todo!()
+        // self.font_cache.bounding_box(font_id, font_size)
     }
 
     pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.em_width(font_id, font_size)
+        todo!()
+        // self.font_cache.em_width(font_id, font_size)
     }
 
     pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.em_advance(font_id, font_size)
+        todo!()
+        // self.font_cache.em_advance(font_id, font_size)
     }
 
     pub fn line_height(&self, font_size: Pixels) -> Pixels {
-        self.font_cache.line_height(font_size)
+        todo!()
+        // self.font_cache.line_height(font_size)
     }
 
     pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.cap_height(font_id, font_size)
+        todo!()
+        // self.font_cache.cap_height(font_id, font_size)
     }
 
     pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.x_height(font_id, font_size)
+        todo!()
+        // self.font_cache.x_height(font_id, font_size)
     }
 
     pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.ascent(font_id, font_size)
+        todo!()
+        // self.font_cache.ascent(font_id, font_size)
     }
 
     pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.descent(font_id, font_size)
+        todo!()
+        // self.font_cache.descent(font_id, font_size)
     }
 
     pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.em_size(font_id, font_size)
+        todo!()
+        // self.font_cache.em_size(font_id, font_size)
     }
 
     pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.font_cache.baseline_offset(font_id, font_size)
+        todo!()
+        // self.font_cache.baseline_offset(font_id, font_size)
     }
 
     pub fn layout_str<'a>(
@@ -235,44 +254,6 @@ impl Display for FontStyle {
     }
 }
 
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
-pub struct FontFeatures {
-    pub calt: Option<bool>,
-    pub case: Option<bool>,
-    pub cpsp: Option<bool>,
-    pub frac: Option<bool>,
-    pub liga: Option<bool>,
-    pub onum: Option<bool>,
-    pub ordn: Option<bool>,
-    pub pnum: Option<bool>,
-    pub ss01: Option<bool>,
-    pub ss02: Option<bool>,
-    pub ss03: Option<bool>,
-    pub ss04: Option<bool>,
-    pub ss05: Option<bool>,
-    pub ss06: Option<bool>,
-    pub ss07: Option<bool>,
-    pub ss08: Option<bool>,
-    pub ss09: Option<bool>,
-    pub ss10: Option<bool>,
-    pub ss11: Option<bool>,
-    pub ss12: Option<bool>,
-    pub ss13: Option<bool>,
-    pub ss14: Option<bool>,
-    pub ss15: Option<bool>,
-    pub ss16: Option<bool>,
-    pub ss17: Option<bool>,
-    pub ss18: Option<bool>,
-    pub ss19: Option<bool>,
-    pub ss20: Option<bool>,
-    pub subs: Option<bool>,
-    pub sups: Option<bool>,
-    pub swsh: Option<bool>,
-    pub titl: Option<bool>,
-    pub tnum: Option<bool>,
-    pub zero: Option<bool>,
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct RunStyle {
     pub color: Hsla,

crates/gpui3/src/text_system/font_features.rs 🔗

@@ -0,0 +1,162 @@
+use schemars::{
+    schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
+    JsonSchema,
+};
+
+macro_rules! create_definitions {
+    ($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
+        #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
+        pub struct FontFeatures {
+            enabled: u64,
+            disabled: u64,
+        }
+
+        impl FontFeatures {
+            $(
+                pub fn $name(&self) -> Option<bool> {
+                    if (self.enabled & (1 << $idx)) != 0 {
+                        Some(true)
+                    } else if (self.disabled & (1 << $idx)) != 0 {
+                        Some(false)
+                    } else {
+                        None
+                    }
+                }
+            )*
+        }
+
+        impl std::fmt::Debug for FontFeatures {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                let mut debug = f.debug_struct("FontFeatures");
+                $(
+                    if let Some(value) = self.$name() {
+                        debug.field(stringify!($name), &value);
+                    };
+                )*
+                debug.finish()
+            }
+        }
+
+        impl<'de> serde::Deserialize<'de> for FontFeatures {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+            where
+                D: serde::Deserializer<'de>,
+            {
+                use serde::de::{MapAccess, Visitor};
+                use std::fmt;
+
+                struct FontFeaturesVisitor;
+
+                impl<'de> Visitor<'de> for FontFeaturesVisitor {
+                    type Value = FontFeatures;
+
+                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                        formatter.write_str("a map of font features")
+                    }
+
+                    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+                    where
+                        M: MapAccess<'de>,
+                    {
+                        let mut enabled: u64 = 0;
+                        let mut disabled: u64 = 0;
+
+                        while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
+                            let idx = match key.as_str() {
+                                $(stringify!($name) => $idx,)*
+                                _ => continue,
+                            };
+                            match value {
+                                Some(true) => enabled |= 1 << idx,
+                                Some(false) => disabled |= 1 << idx,
+                                None => {}
+                            };
+                        }
+                        Ok(FontFeatures { enabled, disabled })
+                    }
+                }
+
+                let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
+                Ok(features)
+            }
+        }
+
+        impl serde::Serialize for FontFeatures {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: serde::Serializer,
+            {
+                use serde::ser::SerializeMap;
+
+                let mut map = serializer.serialize_map(None)?;
+
+                $(
+                    let feature = stringify!($name);
+                    if let Some(value) = self.$name() {
+                        map.serialize_entry(feature, &value)?;
+                    }
+                )*
+
+                map.end()
+            }
+        }
+
+        impl JsonSchema for FontFeatures {
+            fn schema_name() -> String {
+                "FontFeatures".into()
+            }
+
+            fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
+                let mut schema = SchemaObject::default();
+                let properties = &mut schema.object().properties;
+                let feature_schema = Schema::Object(SchemaObject {
+                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
+                    ..Default::default()
+                });
+
+                $(
+                    properties.insert(stringify!($name).to_owned(), feature_schema.clone());
+                )*
+
+                schema.into()
+            }
+        }
+    };
+}
+
+create_definitions!(
+    (calt, 0),
+    (case, 1),
+    (cpsp, 2),
+    (frac, 3),
+    (liga, 4),
+    (onum, 5),
+    (ordn, 6),
+    (pnum, 7),
+    (ss01, 8),
+    (ss02, 9),
+    (ss03, 10),
+    (ss04, 11),
+    (ss05, 12),
+    (ss06, 13),
+    (ss07, 14),
+    (ss08, 15),
+    (ss09, 16),
+    (ss10, 17),
+    (ss11, 18),
+    (ss12, 19),
+    (ss13, 20),
+    (ss14, 21),
+    (ss15, 22),
+    (ss16, 23),
+    (ss17, 24),
+    (ss18, 25),
+    (ss19, 26),
+    (ss20, 27),
+    (subs, 28),
+    (sups, 29),
+    (swsh, 30),
+    (titl, 31),
+    (tnum, 32),
+    (zero, 33)
+);

crates/gpui3/src/text_system/line_wrapper.rs 🔗

@@ -215,7 +215,7 @@ impl Boundary {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{App, FontWeight};
+    use crate::{App, FontFeatures, FontWeight};
 
     #[test]
     fn test_wrap_line() {
@@ -291,7 +291,7 @@ mod tests {
             let text_system = cx.text_system().clone();
 
             let family = text_system
-                .load_font_family(&["Helvetica"], &Default::default())
+                .load_font_family(&["Helvetica"], &FontFeatures::default())
                 .unwrap();
             let font_id = text_system
                 .select_font(family, Default::default(), Default::default())