Checkpoint

Nathan Sobo created

Change summary

Cargo.lock                                        |  10 
crates/gpui/src/text_layout.rs                    |  34 
crates/gpui3/Cargo.toml                           |   2 
crates/gpui3/src/geometry.rs                      |  66 +++
crates/gpui3/src/platform.rs                      |   4 
crates/gpui3/src/platform/mac/text_system.rs      |  28 -
crates/gpui3/src/text_system.rs                   | 145 +++++--
crates/gpui3/src/text_system/font_cache.rs        | 302 -----------------
crates/gpui3/src/text_system/text_layout_cache.rs | 191 +++++-----
crates/zed/Cargo.toml                             |   1 
10 files changed, 299 insertions(+), 484 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5127,6 +5127,15 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 
+[[package]]
+name = "owning_ref"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
+dependencies = [
+ "stable_deref_trait",
+]
+
 [[package]]
 name = "parity-tokio-ipc"
 version = "0.9.0"
@@ -9985,6 +9994,7 @@ dependencies = [
  "node_runtime",
  "num_cpus",
  "outline",
+ "owning_ref",
  "parking_lot 0.11.2",
  "plugin_runtime",
  "postage",

crates/gpui/src/text_layout.rs 🔗

@@ -22,8 +22,8 @@ use std::{
 };
 
 pub struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
+    curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
     fonts: Arc<dyn platform::FontSystem>,
 }
 
@@ -56,7 +56,7 @@ impl TextLayoutCache {
         font_size: f32,
         runs: &'a [(usize, RunStyle)],
     ) -> Line {
-        let key = &CacheKeyRef {
+        let key = &BorrowedCacheKey {
             text,
             font_size: OrderedFloat(font_size),
             runs,
@@ -72,7 +72,7 @@ impl TextLayoutCache {
             Line::new(layout, runs)
         } else {
             let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
-            let key = CacheKeyValue {
+            let key = OwnedCacheKey {
                 text: text.into(),
                 font_size: OrderedFloat(font_size),
                 runs: SmallVec::from(runs),
@@ -84,7 +84,7 @@ impl TextLayoutCache {
 }
 
 trait CacheKey {
-    fn key(&self) -> CacheKeyRef;
+    fn key(&self) -> BorrowedCacheKey;
 }
 
 impl<'a> PartialEq for (dyn CacheKey + 'a) {
@@ -102,15 +102,15 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
 }
 
 #[derive(Eq)]
-struct CacheKeyValue {
+struct OwnedCacheKey {
     text: String,
     font_size: OrderedFloat<f32>,
     runs: SmallVec<[(usize, RunStyle); 1]>,
 }
 
-impl CacheKey for CacheKeyValue {
-    fn key(&self) -> CacheKeyRef {
-        CacheKeyRef {
+impl CacheKey for OwnedCacheKey {
+    fn key(&self) -> BorrowedCacheKey {
+        BorrowedCacheKey {
             text: self.text.as_str(),
             font_size: self.font_size,
             runs: self.runs.as_slice(),
@@ -118,38 +118,38 @@ impl CacheKey for CacheKeyValue {
     }
 }
 
-impl PartialEq for CacheKeyValue {
+impl PartialEq for OwnedCacheKey {
     fn eq(&self, other: &Self) -> bool {
         self.key().eq(&other.key())
     }
 }
 
-impl Hash for CacheKeyValue {
+impl Hash for OwnedCacheKey {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.key().hash(state);
     }
 }
 
-impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
+impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
     fn borrow(&self) -> &(dyn CacheKey + 'a) {
         self as &dyn CacheKey
     }
 }
 
 #[derive(Copy, Clone)]
-struct CacheKeyRef<'a> {
+struct BorrowedCacheKey<'a> {
     text: &'a str,
     font_size: OrderedFloat<f32>,
     runs: &'a [(usize, RunStyle)],
 }
 
-impl<'a> CacheKey for CacheKeyRef<'a> {
-    fn key(&self) -> CacheKeyRef {
+impl<'a> CacheKey for BorrowedCacheKey<'a> {
+    fn key(&self) -> BorrowedCacheKey {
         *self
     }
 }
 
-impl<'a> PartialEq for CacheKeyRef<'a> {
+impl<'a> PartialEq for BorrowedCacheKey<'a> {
     fn eq(&self, other: &Self) -> bool {
         self.text == other.text
             && self.font_size == other.font_size
@@ -162,7 +162,7 @@ impl<'a> PartialEq for CacheKeyRef<'a> {
     }
 }
 
-impl<'a> Hash for CacheKeyRef<'a> {
+impl<'a> Hash for BorrowedCacheKey<'a> {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.text.hash(state);
         self.font_size.hash(state);

crates/gpui3/Cargo.toml 🔗

@@ -2,7 +2,7 @@
 name = "gpui3"
 version = "0.1.0"
 edition = "2021"
-authors = ["Nathan Sobo <nathansobo@gmail.com>"]
+authors = ["Nathan Sobo <nathan@zed.dev>"]
 description = "The next version of Zed's GPU-accelerated UI framework"
 publish = false
 

crates/gpui3/src/geometry.rs 🔗

@@ -4,9 +4,7 @@ use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
 use refineable::Refineable;
 use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
 
-#[derive(
-    Refineable, Default, Add, AddAssign, Sub, SubAssign, Mul, Copy, Debug, PartialEq, Eq, Hash,
-)]
+#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Point<T: Clone + Debug> {
@@ -31,6 +29,17 @@ impl<T: Clone + Debug> Point<T> {
     }
 }
 
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> Mul<S> for Point<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: S) -> Self::Output {
+        Self {
+            x: self.x.clone() * rhs.clone(),
+            y: self.y.clone() * rhs,
+        }
+    }
+}
+
 impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Point<T> {
     fn mul_assign(&mut self, rhs: S) {
         self.x = self.x.clone() * rhs.clone();
@@ -116,6 +125,17 @@ impl<T: Clone + Debug> Size<T> {
     }
 }
 
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> Mul<S> for Size<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: S) -> Self::Output {
+        Self {
+            width: self.width.clone() * rhs.clone(),
+            height: self.height.clone() * rhs,
+        }
+    }
+}
+
 impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
     fn mul_assign(&mut self, rhs: S) {
         self.width = self.width.clone() * rhs.clone();
@@ -170,6 +190,20 @@ pub struct Bounds<T: Clone + Debug> {
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
 
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> Mul<S> for Bounds<T>
+where
+    T: Mul<S, Output = T>,
+{
+    type Output = Self;
+
+    fn mul(self, rhs: S) -> Self::Output {
+        Self {
+            origin: self.origin * rhs.clone(),
+            size: self.size * rhs,
+        }
+    }
+}
+
 impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Bounds<T> {
     fn mul_assign(&mut self, rhs: S) {
         self.origin *= rhs.clone();
@@ -235,6 +269,19 @@ pub struct Edges<T: Clone + Debug> {
     pub left: T,
 }
 
+impl<T: Clone + Debug + Mul<Output = T>> Mul for Edges<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top: self.top.clone() * rhs.top,
+            right: self.right.clone() * rhs.right,
+            bottom: self.bottom.clone() * rhs.bottom,
+            left: self.left.clone() * rhs.left,
+        }
+    }
+}
+
 impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T> {
     fn mul_assign(&mut self, rhs: S) {
         self.top = self.top.clone() * rhs.clone();
@@ -317,6 +364,19 @@ pub struct Corners<T: Clone + Debug> {
     pub bottom_left: T,
 }
 
+impl<T: Clone + Debug + Mul<Output = T>> Mul for Corners<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top_left: self.top_left.clone() * rhs.top_left,
+            top_right: self.top_right.clone() * rhs.top_right,
+            bottom_right: self.bottom_right.clone() * rhs.bottom_right,
+            bottom_left: self.bottom_left.clone() * rhs.bottom_left,
+        }
+    }
+}
+
 impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T> {
     fn mul_assign(&mut self, rhs: S) {
         self.top_left = self.top_left.clone() * rhs.clone();

crates/gpui3/src/platform.rs 🔗

@@ -156,8 +156,8 @@ pub trait PlatformDispatcher: Send + Sync {
 pub trait PlatformTextSystem: Send + Sync {
     fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
     fn all_font_families(&self) -> Vec<String>;
-    fn select_font(&self, descriptor: Font) -> Result<FontId>;
-    fn font_metrics(&self, font_id: FontId) -> Arc<FontMetrics>;
+    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
     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>;

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

@@ -47,7 +47,7 @@ struct TextSystemState {
     system_source: SystemSource,
     fonts: Vec<FontKitFont>,
     font_selections: HashMap<Font, FontId>,
-    font_metrics: HashMap<FontId, Arc<FontMetrics>>,
+    font_metrics: HashMap<FontId, FontMetrics>,
     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>,
@@ -87,9 +87,9 @@ impl PlatformTextSystem for MacTextSystem {
             .expect("core text should never return an error")
     }
 
-    fn select_font(&self, font: Font) -> Result<FontId> {
+    fn font_id(&self, font: &Font) -> Result<FontId> {
         let lock = self.0.upgradable_read();
-        if let Some(font_id) = lock.font_selections.get(&font) {
+        if let Some(font_id) = lock.font_selections.get(font) {
             Ok(*font_id)
         } else {
             let mut lock = parking_lot::RwLockUpgradableReadGuard::upgrade(lock);
@@ -121,20 +121,14 @@ impl PlatformTextSystem for MacTextSystem {
         }
     }
 
-    fn font_metrics(&self, font_id: FontId) -> Arc<FontMetrics> {
-        let lock = self.0.upgradable_read();
-        if let Some(metrics) = lock.font_metrics.get(&font_id) {
-            metrics.clone()
-        } else {
-            let mut lock = parking_lot::RwLockUpgradableReadGuard::upgrade(lock);
-            let metrics: Arc<FontMetrics> = Arc::new(lock.fonts[font_id.0].metrics().into());
-            lock.font_metrics.insert(font_id, metrics.clone());
-            metrics
-        }
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
+        self.0.read().fonts[font_id.0].metrics().into()
     }
 
     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
-        self.0.read().typographic_bounds(font_id, glyph_id)
+        Ok(self.0.read().fonts[font_id.0]
+            .typographic_bounds(glyph_id.into())?
+            .into())
     }
 
     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
@@ -214,12 +208,6 @@ impl TextSystemState {
         Ok(font_ids)
     }
 
-    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) -> Result<Size<f32>> {
         Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
     }

crates/gpui3/src/text_system.rs 🔗

@@ -2,18 +2,18 @@ mod font_features;
 mod line_wrapper;
 mod text_layout_cache;
 
+use anyhow::anyhow;
 pub use font_features::*;
 use line_wrapper::*;
 pub use text_layout_cache::*;
 
 use crate::{
-    px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
+    px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, UnderlineStyle,
 };
 use collections::HashMap;
 use core::fmt;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
 use std::{
-    borrow::BorrowMut,
     fmt::{Debug, Display, Formatter},
     hash::{Hash, Hasher},
     ops::{Deref, DerefMut},
@@ -29,33 +29,65 @@ pub struct FontFamilyId(pub usize);
 pub struct TextSystem {
     text_layout_cache: Arc<TextLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
-    wrapper_pool: Mutex<HashMap<(Font, Pixels), Vec<LineWrapper>>>,
+    font_ids_by_font: RwLock<HashMap<Font, FontId>>,
+    fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
+    font_metrics: RwLock<HashMap<Font, FontMetrics>>,
+    wrapper_pool: Mutex<HashMap<FontWithSize, Vec<LineWrapper>>>,
 }
 
 impl TextSystem {
     pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
         TextSystem {
             text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
-            wrapper_pool: Mutex::new(HashMap::default()),
             platform_text_system,
+            font_metrics: RwLock::new(HashMap::default()),
+            font_ids_by_font: RwLock::new(HashMap::default()),
+            fonts_by_font_id: RwLock::new(HashMap::default()),
+            wrapper_pool: Mutex::new(HashMap::default()),
         }
     }
 
-    pub fn select_font(&self, descriptor: impl Into<Font>) -> Result<FontId> {
-        self.platform_text_system.select_font(descriptor.into())
+    pub fn font_id(&self, font: &Font) -> Result<FontId> {
+        if let Some(font_id) = self.font_ids_by_font.read().get(font) {
+            Ok(*font_id)
+        } else {
+            let font_id = self.platform_text_system.font_id(font)?;
+            self.font_ids_by_font.write().insert(font.clone(), font_id);
+            self.fonts_by_font_id.write().insert(font_id, font.clone());
+
+            Ok(font_id)
+        }
     }
 
-    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
-        let metrics = self.platform_text_system.font_metrics(font_id);
-        metrics.bounding_box(font_size);
+    pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
+        self.fonts_by_font_id
+            .read()
+            .get(&font_id)
+            .ok_or_else(|| anyhow!("font not found"))
+            .map(|font| f(self, font))
+    }
 
-        todo!()
-        // self.font_cache.bounding_box(font_id, font_size)
+    pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
+        self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
     }
 
-    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.em_width(font_id, font_size)
+    pub fn typographic_bounds(
+        &self,
+        font: &Font,
+        font_size: Pixels,
+        character: char,
+    ) -> Result<Bounds<Pixels>> {
+        let font_id = self.font_id(font)?;
+        let glyph_id = self
+            .platform_text_system
+            .glyph_for_char(font_id, character)
+            .ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
+        let bounds = self
+            .platform_text_system
+            .typographic_bounds(font_id, glyph_id)?;
+        self.read_metrics(font, |metrics| {
+            (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
+        })
     }
 
     pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
@@ -68,34 +100,41 @@ impl TextSystem {
         // self.font_cache.line_height(font_size)
     }
 
-    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.cap_height(font_id, font_size)
+    pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.cap_height(font_size))
     }
 
-    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.x_height(font_id, font_size)
+    pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.x_height(font_size))
     }
 
-    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.ascent(font_id, font_size)
+    pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.ascent(font_size))
     }
 
-    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.descent(font_id, font_size)
+    pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.descent(font_size))
     }
 
-    pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.em_size(font_id, font_size)
+    pub fn baseline_offset(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        let line_height = self.line_height(font_size);
+        let ascent = self.ascent(font, font_size)?;
+        let descent = self.descent(font, font_size)?;
+        let padding_top = (line_height - ascent - descent) / 2.;
+        Ok(padding_top + ascent)
     }
 
-    pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        todo!()
-        // self.font_cache.baseline_offset(font_id, font_size)
+    fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
+        if let Some(metrics) = self.font_metrics.read().get(font) {
+            Ok(read(metrics))
+        } else {
+            let font_id = self.platform_text_system.font_id(&font)?;
+            let mut lock = self.font_metrics.write();
+            let metrics = lock
+                .entry(font.clone())
+                .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
+            Ok(read(metrics))
+        }
     }
 
     pub fn layout_str<'a>(
@@ -113,7 +152,12 @@ impl TextSystem {
 
     pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
         let lock = &mut self.wrapper_pool.lock();
-        let wrappers = lock.entry((font.clone(), font_size)).or_default();
+        let wrappers = lock
+            .entry(FontWithSize {
+                font: font.clone(),
+                font_size,
+            })
+            .or_default();
         let wrapper = wrappers.pop().unwrap_or_else(|| {
             LineWrapper::new(font, font_size, self.platform_text_system.clone())
         });
@@ -125,6 +169,12 @@ impl TextSystem {
     }
 }
 
+#[derive(Hash, Eq, PartialEq)]
+struct FontWithSize {
+    font: Font,
+    font_size: Pixels,
+}
+
 pub struct LineWrapperHandle {
     wrapper: Option<LineWrapper>,
     text_system: Arc<TextSystem>,
@@ -135,7 +185,10 @@ impl Drop for LineWrapperHandle {
         let mut state = self.text_system.wrapper_pool.lock();
         let wrapper = self.wrapper.take().unwrap();
         state
-            .get_mut(&(wrapper.font.clone(), wrapper.font_size))
+            .get_mut(&FontWithSize {
+                font: wrapper.font.clone(),
+                font_size: wrapper.font_size,
+            })
             .unwrap()
             .push(wrapper);
     }
@@ -329,49 +382,43 @@ pub struct FontMetrics {
 }
 
 impl FontMetrics {
-    /// Returns the number of pixels that make up the "em square",
-    /// a scalable grid for determining the size of a typeface.
-    pub fn units_per_em(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.units_per_em as f32 / font_size.0).ceil())
-    }
-
     /// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
     pub fn ascent(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.ascent / font_size.0).ceil() as f32)
+        Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
     pub fn descent(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.descent / font_size.0).ceil() as f32)
+        Pixels((self.descent / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the recommended additional space to add between lines of type in pixels.
     pub fn line_gap(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.line_gap / font_size.0).ceil() as f32)
+        Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the suggested position of the underline in pixels.
     pub fn underline_position(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.underline_position / font_size.0).ceil() as f32)
+        Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the suggested thickness of the underline in pixels.
     pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.underline_thickness / font_size.0).ceil() as f32)
+        Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the height of a capital letter measured from the baseline of the font in pixels.
     pub fn cap_height(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.cap_height / font_size.0).ceil() as f32)
+        Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the height of a lowercase x in pixels.
     pub fn x_height(&self, font_size: Pixels) -> Pixels {
-        Pixels((self.x_height / font_size.0).ceil() as f32)
+        Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
     }
 
     /// Returns the outer limits of the area that the font covers in pixels.
     pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
-        (self.bounding_box / font_size.0).map(px)
+        (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
     }
 }

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

@@ -1,302 +0,0 @@
-use crate::{
-    px, Bounds, FontFeatures, FontStyle, FontWeight, Pixels, PlatformTextSystem, Result, Size,
-};
-use anyhow::anyhow;
-use parking_lot::{RwLock, RwLockUpgradableReadGuard};
-use std::{collections::HashMap, sync::Arc};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct FontFamilyId(usize);
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct FontId(pub usize);
-
-pub(crate) struct FontCache(RwLock<FontCacheState>);
-
-pub(crate) struct FontCacheState {
-    platform_text_system: Arc<dyn PlatformTextSystem>,
-    families: Vec<Family>,
-    default_family: Option<FontFamilyId>,
-    font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
-    metrics: HashMap<FontId, FontMetrics>,
-}
-
-unsafe impl Send for FontCache {}
-
-impl FontCache {
-    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
-        Self(RwLock::new(FontCacheState {
-            platform_text_system: fonts,
-            families: Default::default(),
-            default_family: None,
-            font_selections: Default::default(),
-            metrics: Default::default(),
-        }))
-    }
-
-    pub fn family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
-        self.0
-            .read()
-            .families
-            .get(family_id.0)
-            .ok_or_else(|| anyhow!("invalid family id"))
-            .map(|family| family.name.clone())
-    }
-
-    pub fn load_family(&self, names: &[&str], features: &FontFeatures) -> Result<FontFamilyId> {
-        for name in names {
-            let state = self.0.upgradable_read();
-
-            if let Some(ix) = state
-                .families
-                .iter()
-                .position(|f| f.name.as_ref() == *name && f.font_features == *features)
-            {
-                return Ok(FontFamilyId(ix));
-            }
-
-            let mut state = RwLockUpgradableReadGuard::upgrade(state);
-
-            if let Ok(font_ids) = state.platform_text_system.load_family(name, features) {
-                if font_ids.is_empty() {
-                    continue;
-                }
-
-                let family_id = FontFamilyId(state.families.len());
-                for font_id in &font_ids {
-                    if state
-                        .platform_text_system
-                        .glyph_for_char(*font_id, 'm')
-                        .is_none()
-                    {
-                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
-                    }
-                }
-
-                state.families.push(Family {
-                    name: Arc::from(*name),
-                    font_features: features.clone(),
-                    font_ids,
-                });
-                return Ok(family_id);
-            }
-        }
-
-        Err(anyhow!(
-            "could not find a non-empty font family matching one of the given names"
-        ))
-    }
-
-    /// Returns an arbitrary font family that is available on the system.
-    pub fn known_existing_family(&self) -> FontFamilyId {
-        if let Some(family_id) = self.0.read().default_family {
-            return family_id;
-        }
-
-        let default_family = self
-            .load_family(
-                &["Courier", "Helvetica", "Arial", "Verdana"],
-                &Default::default(),
-            )
-            .unwrap_or_else(|_| {
-                let all_family_names = self.0.read().platform_text_system.all_families();
-                let all_family_names: Vec<_> = all_family_names
-                    .iter()
-                    .map(|string| string.as_str())
-                    .collect();
-                self.load_family(&all_family_names, &Default::default())
-                    .expect("could not load any default font family")
-            });
-
-        self.0.write().default_family = Some(default_family);
-        default_family
-    }
-
-    pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
-        self.select_font(family_id, Default::default(), Default::default())
-            .unwrap()
-    }
-
-    pub fn select_font(
-        &self,
-        family_id: FontFamilyId,
-        weight: FontWeight,
-        style: FontStyle,
-    ) -> Result<FontId> {
-        let inner = self.0.upgradable_read();
-        if let Some(font_id) = inner
-            .font_selections
-            .get(&family_id)
-            .and_then(|fonts| fonts.get(&(weight, style)))
-        {
-            Ok(*font_id)
-        } else {
-            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
-            let family = &inner.families[family_id.0];
-            let font_id = inner
-                .platform_text_system
-                .select_font(&family.font_ids, weight, style)
-                .unwrap_or(family.font_ids[0]);
-            inner
-                .font_selections
-                .entry(family_id)
-                .or_default()
-                .insert((weight, style), font_id);
-            Ok(font_id)
-        }
-    }
-
-    pub fn read_metric<F, T>(&self, font_id: FontId, f: F) -> T
-    where
-        F: FnOnce(&FontMetrics) -> T,
-        T: 'static,
-    {
-        let state = self.0.upgradable_read();
-        if let Some(metrics) = state.metrics.get(&font_id) {
-            f(metrics)
-        } else {
-            let metrics = state.platform_text_system.font_metrics(font_id);
-            let metric = f(&metrics);
-            let mut state = RwLockUpgradableReadGuard::upgrade(state);
-            state.metrics.insert(font_id, metrics);
-            metric
-        }
-    }
-
-    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
-        let bounding_box = self.read_metric(font_id, |m| m.bounding_box);
-
-        let width = px(bounding_box.size.width) * self.em_size(font_id, font_size);
-        let height = px(bounding_box.size.height) * self.em_size(font_id, font_size);
-        Size { width, height }
-    }
-
-    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        let glyph_id;
-        let bounds;
-        {
-            let state = self.0.read();
-            glyph_id = state
-                .platform_text_system
-                .glyph_for_char(font_id, 'm')
-                .unwrap();
-            bounds = state
-                .platform_text_system
-                .typographic_bounds(font_id, glyph_id)
-                .unwrap();
-        }
-        self.em_size(font_id, font_size) * bounds.size.width
-    }
-
-    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        let glyph_id;
-        let advance;
-        {
-            let state = self.0.read();
-            glyph_id = state
-                .platform_text_system
-                .glyph_for_char(font_id, 'm')
-                .unwrap();
-            advance = state
-                .platform_text_system
-                .advance(font_id, glyph_id)
-                .unwrap();
-        }
-        self.em_size(font_id, font_size) * advance.width
-    }
-
-    pub fn line_height(&self, font_size: Pixels) -> Pixels {
-        (font_size * 1.618).round()
-    }
-
-    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.cap_height)
-    }
-
-    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.x_height)
-    }
-
-    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.ascent)
-    }
-
-    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.em_size(font_id, font_size) * self.read_metric(font_id, |m| -m.descent)
-    }
-
-    pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        font_size / self.read_metric(font_id, |m| m.units_per_em as f32)
-    }
-
-    pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        let line_height = self.line_height(font_size);
-        let ascent = self.ascent(font_id, font_size);
-        let descent = self.descent(font_id, font_size);
-        let padding_top = (line_height - ascent - descent) / 2.;
-        padding_top + ascent
-    }
-}
-
-#[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>,
-}
-
-struct Family {
-    name: Arc<str>,
-    font_features: FontFeatures,
-    font_ids: Vec<FontId>,
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{FontStyle, FontWeight, Platform, TestPlatform};
-
-    #[test]
-    fn test_select_font() {
-        let platform = TestPlatform::new();
-        let fonts = FontCache::new(platform.text_system());
-        let arial = fonts
-            .load_family(
-                &["Arial"],
-                &FontFeatures {
-                    calt: Some(false),
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-        let arial_regular = fonts
-            .select_font(arial, FontWeight::default(), FontStyle::default())
-            .unwrap();
-        let arial_italic = fonts
-            .select_font(arial, FontWeight::default(), FontStyle::Italic)
-            .unwrap();
-        let arial_bold = fonts
-            .select_font(arial, FontWeight::BOLD, FontStyle::default())
-            .unwrap();
-        assert_ne!(arial_regular, arial_italic);
-        assert_ne!(arial_regular, arial_bold);
-        assert_ne!(arial_italic, arial_bold);
-
-        let arial_with_calt = fonts
-            .load_family(
-                &["Arial"],
-                &FontFeatures {
-                    calt: Some(true),
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-        assert_ne!(arial_with_calt, arial);
-    }
-}

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

@@ -2,6 +2,7 @@ use crate::{
     black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
     Run, RunStyle, UnderlineStyle, WindowContext,
 };
+use anyhow::Result;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
 use std::{
@@ -262,79 +263,86 @@ impl Line {
         let mut underline = None;
 
         for run in &self.layout.runs {
-            let max_glyph_width = cx
-                .text_system()
-                .bounding_box(run.font_id, self.layout.font_size)
-                .width;
-
-            for glyph in &run.glyphs {
-                let glyph_origin = origin + baseline_offset + glyph.position;
-                if glyph_origin.x > visible_bounds.upper_right().x {
-                    break;
-                }
+            cx.text_system().with_font(run.font_id, |system, font| {
+                let max_glyph_width = cx
+                    .text_system()
+                    .bounding_box(font, self.layout.font_size)?
+                    .size
+                    .width;
+
+                for glyph in &run.glyphs {
+                    let glyph_origin = origin + baseline_offset + glyph.position;
+                    if glyph_origin.x > visible_bounds.upper_right().x {
+                        break;
+                    }
 
-                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-                if glyph.index >= run_end {
-                    if let Some(style_run) = style_runs.next() {
-                        if let Some((_, underline_style)) = &mut underline {
-                            if style_run.underline != *underline_style {
-                                finished_underline = underline.take();
+                    let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+                    if glyph.index >= run_end {
+                        if let Some(style_run) = style_runs.next() {
+                            if let Some((_, underline_style)) = &mut underline {
+                                if style_run.underline != *underline_style {
+                                    finished_underline = underline.take();
+                                }
                             }
+                            if style_run.underline.thickness > px(0.) {
+                                underline.get_or_insert((
+                                    point(
+                                        glyph_origin.x,
+                                        origin.y
+                                            + baseline_offset.y
+                                            + (self.layout.descent * 0.618),
+                                    ),
+                                    UnderlineStyle {
+                                        color: style_run.underline.color,
+                                        thickness: style_run.underline.thickness,
+                                        squiggly: style_run.underline.squiggly,
+                                    },
+                                ));
+                            }
+
+                            run_end += style_run.len as usize;
+                            color = style_run.color;
+                        } else {
+                            run_end = self.layout.len;
+                            finished_underline = underline.take();
                         }
-                        if style_run.underline.thickness > px(0.) {
-                            underline.get_or_insert((
-                                point(
-                                    glyph_origin.x,
-                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
-                                ),
-                                UnderlineStyle {
-                                    color: style_run.underline.color,
-                                    thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
-                                },
-                            ));
-                        }
+                    }
 
-                        run_end += style_run.len as usize;
-                        color = style_run.color;
-                    } else {
-                        run_end = self.layout.len;
-                        finished_underline = underline.take();
+                    if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
+                        continue;
                     }
-                }
 
-                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
-                    continue;
-                }
+                    if let Some((_underline_origin, _underline_style)) = finished_underline {
+                        // cx.scene().insert(Underline {
+                        //     origin: underline_origin,
+                        //     width: glyph_origin.x - underline_origin.x,
+                        //     thickness: underline_style.thickness.into(),
+                        //     color: underline_style.color.unwrap(),
+                        //     squiggly: underline_style.squiggly,
+                        // });
+                    }
 
-                if let Some((_underline_origin, _underline_style)) = finished_underline {
-                    // cx.scene().insert(Underline {
-                    //     origin: underline_origin,
-                    //     width: glyph_origin.x - underline_origin.x,
-                    //     thickness: underline_style.thickness.into(),
-                    //     color: underline_style.color.unwrap(),
-                    //     squiggly: underline_style.squiggly,
-                    // });
+                    // todo!()
+                    // if glyph.is_emoji {
+                    //     cx.scene().push_image_glyph(scene::ImageGlyph {
+                    //         font_id: run.font_id,
+                    //         font_size: self.layout.font_size,
+                    //         id: glyph.id,
+                    //         origin: glyph_origin,
+                    //     });
+                    // } else {
+                    //     cx.scene().push_glyph(scene::Glyph {
+                    //         font_id: run.font_id,
+                    //         font_size: self.layout.font_size,
+                    //         id: glyph.id,
+                    //         origin: glyph_origin,
+                    //         color,
+                    //     });
+                    // }
                 }
 
-                // todo!()
-                // if glyph.is_emoji {
-                //     cx.scene().push_image_glyph(scene::ImageGlyph {
-                //         font_id: run.font_id,
-                //         font_size: self.layout.font_size,
-                //         id: glyph.id,
-                //         origin: glyph_origin,
-                //     });
-                // } else {
-                //     cx.scene().push_glyph(scene::Glyph {
-                //         font_id: run.font_id,
-                //         font_size: self.layout.font_size,
-                //         id: glyph.id,
-                //         origin: glyph_origin,
-                //         color,
-                //     });
-                // }
-            }
+                anyhow::Ok(())
+            });
         }
 
         if let Some((_underline_start, _underline_style)) = underline.take() {
@@ -356,7 +364,7 @@ impl Line {
         line_height: Pixels,
         boundaries: &[ShapedBoundary],
         cx: &mut WindowContext,
-    ) {
+    ) -> Result<()> {
         let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
         let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
 
@@ -434,31 +442,32 @@ impl Line {
                     // });
                 }
 
-                let _glyph_bounds = Bounds {
-                    origin: glyph_origin,
-                    size: cx
-                        .text_system()
-                        .bounding_box(run.font_id, self.layout.font_size),
-                };
-                // todo!()
-                // if glyph_bounds.intersects(visible_bounds) {
-                //     if glyph.is_emoji {
-                //         cx.scene().push_image_glyph(scene::ImageGlyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //         });
-                //     } else {
-                //         cx.scene().push_glyph(scene::Glyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //             color,
-                //         });
-                //     }
-                // }
+                cx.text_system().with_font(run.font_id, |system, font| {
+                    let _glyph_bounds = Bounds {
+                        origin: glyph_origin,
+                        size: system.bounding_box(font, self.layout.font_size)?.size,
+                    };
+                    // todo!()
+                    // if glyph_bounds.intersects(visible_bounds) {
+                    //     if glyph.is_emoji {
+                    //         cx.scene().push_image_glyph(scene::ImageGlyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //         });
+                    //     } else {
+                    //         cx.scene().push_glyph(scene::Glyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //             color,
+                    //         });
+                    //     }
+                    // }
+                    anyhow::Ok(())
+                })?;
             }
         }
 
@@ -472,6 +481,8 @@ impl Line {
             //     squiggly: underline_style.squiggly,
             // });
         }
+
+        Ok(())
     }
 }
 

crates/zed/Cargo.toml 🔗

@@ -137,6 +137,7 @@ tree-sitter-nu.workspace = true
 url = "2.2"
 urlencoding = "2.1.2"
 uuid = { version = "1.1.2", features = ["v4"] }
+owning_ref = "0.4.1"
 
 [dev-dependencies]
 call = { path = "../call", features = ["test-support"] }