Checkpoint

Nathan Sobo created

Change summary

crates/gpui/src/platform/mac/platform.rs          |   6 
crates/gpui3/Cargo.toml                           |   2 
crates/gpui3/src/app.rs                           |  29 
crates/gpui3/src/elements/div.rs                  |   2 
crates/gpui3/src/gpui3.rs                         |   7 
crates/gpui3/src/platform.rs                      |   8 
crates/gpui3/src/platform/mac/platform.rs         |   4 
crates/gpui3/src/platform/mac/text_system.rs      |  26 
crates/gpui3/src/platform/mac/window.rs           |  14 
crates/gpui3/src/scene.rs                         |   2 
crates/gpui3/src/style.rs                         |   4 
crates/gpui3/src/text_system.rs                   | 326 +++++++++++++
crates/gpui3/src/text_system/font_cache.rs        | 177 +-----
crates/gpui3/src/text_system/line_wrapper.rs      | 347 ++++++++++++++
crates/gpui3/src/text_system/text_layout_cache.rs | 418 ----------------
crates/gpui3/src/window.rs                        |   4 
16 files changed, 794 insertions(+), 582 deletions(-)

Detailed changes

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -1,6 +1,6 @@
 use super::{
     event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
-    MacWindow, TextSystem,
+    FontSystem, MacWindow,
 };
 use crate::{
     executor,
@@ -488,7 +488,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
 
 pub struct MacPlatform {
     dispatcher: Arc<Dispatcher>,
-    fonts: Arc<TextSystem>,
+    fonts: Arc<FontSystem>,
     pasteboard: id,
     text_hash_pasteboard_type: id,
     metadata_pasteboard_type: id,
@@ -498,7 +498,7 @@ impl MacPlatform {
     pub fn new() -> Self {
         Self {
             dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(TextSystem::new()),
+            fonts: Arc::new(FontSystem::new()),
             pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
             text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
             metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },

crates/gpui3/Cargo.toml 🔗

@@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework"
 publish = false
 
 [features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
+test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
 
 [lib]
 path = "src/gpui3.rs"

crates/gpui3/src/app.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    Context, FontCache, LayoutId, Platform, Reference, View, Window, WindowContext, WindowHandle,
-    WindowId,
+    current_platform, Context, LayoutId, Platform, Reference, TextSystem, View, Window,
+    WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use slotmap::SlotMap;
@@ -10,14 +10,25 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
 pub struct App(Rc<RefCell<AppContext>>);
 
 impl App {
-    pub fn new(platform: Rc<dyn Platform>) -> Self {
-        Self(Rc::new(RefCell::new(AppContext::new(platform))))
+    pub fn new() -> Self {
+        Self(Rc::new(RefCell::new(AppContext::new(current_platform()))))
+    }
+
+    pub fn run<F>(self, on_finish_launching: F)
+    where
+        F: 'static + FnOnce(&mut AppContext),
+    {
+        let platform = self.0.borrow().platform().clone();
+        platform.run(Box::new(move || {
+            let mut cx = self.0.borrow_mut();
+            on_finish_launching(&mut *cx);
+        }));
     }
 }
 
 pub struct AppContext {
     platform: Rc<dyn Platform>,
-    font_cache: Arc<FontCache>,
+    text_system: Arc<TextSystem>,
     pub(crate) entities: SlotMap<EntityId, Option<Box<dyn Any>>>,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     // We recycle this memory across layout requests.
@@ -26,10 +37,10 @@ pub struct AppContext {
 
 impl AppContext {
     pub fn new(platform: Rc<dyn Platform>) -> Self {
-        let font_cache = Arc::new(FontCache::new(platform.text_system()));
+        let text_system = Arc::new(TextSystem::new(platform.text_system()));
         AppContext {
             platform,
-            font_cache,
+            text_system,
             entities: SlotMap::with_key(),
             windows: SlotMap::with_key(),
             layout_id_buffer: Default::default(),
@@ -45,8 +56,8 @@ impl AppContext {
         &self.platform
     }
 
-    pub fn font_cache(&self) -> &Arc<FontCache> {
-        &self.font_cache
+    pub fn text_system(&self) -> &Arc<TextSystem> {
+        &self.text_system
     }
 
     pub fn open_window<S: 'static>(

crates/gpui3/src/elements/div.rs 🔗

@@ -47,7 +47,7 @@ impl<S: 'static> Element for Div<S> {
             cx.pop_text_style();
         }
 
-        Ok((cx.request_layout(style, children.clone())?, children))
+        Ok((cx.request_layout(style.into(), children.clone())?, children))
     }
 
     fn paint(

crates/gpui3/src/gpui3.rs 🔗

@@ -3,7 +3,6 @@ mod color;
 mod element;
 mod elements;
 mod executor;
-mod fonts;
 mod geometry;
 mod platform;
 mod renderer;
@@ -11,7 +10,7 @@ mod scene;
 mod style;
 mod styled;
 mod taffy;
-mod text;
+mod text_system;
 mod util;
 mod window;
 
@@ -21,7 +20,6 @@ pub use color::*;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;
-pub use fonts::*;
 pub use geometry::*;
 pub use platform::*;
 pub use refineable::*;
@@ -33,8 +31,7 @@ pub use style::*;
 pub use styled::*;
 pub use taffy::LayoutId;
 use taffy::TaffyLayoutEngine;
-use text::*;
-pub use text::{Glyph, GlyphId};
+pub use text_system::*;
 pub use util::arc_cow::ArcCow;
 pub use window::*;
 

crates/gpui3/src/platform.rs 🔗

@@ -35,10 +35,10 @@ pub use mac::*;
 #[cfg(any(test, feature = "test"))]
 pub use test::*;
 
-// #[cfg(target_os = "macos")]
-// pub fn current() -> Rc<dyn Platform> {
-//     MacPlatform
-// }
+#[cfg(target_os = "macos")]
+pub(crate) fn current_platform() -> Rc<dyn Platform> {
+    Rc::new(MacPlatform::new())
+}
 
 pub trait Platform {
     fn executor(&self) -> Rc<ForegroundExecutor>;

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

@@ -529,7 +529,7 @@ impl Platform for MacPlatform {
                     None
                 };
 
-                if let Some(mut done_tx) = done_tx.take() {
+                if let Some(done_tx) = done_tx.take() {
                     let _ = done_tx.send(result);
                 }
             });
@@ -557,7 +557,7 @@ impl Platform for MacPlatform {
                     }
                 }
 
-                if let Some(mut done_tx) = done_tx.take() {
+                if let Some(done_tx) = done_tx.take() {
                     let _ = done_tx.send(result);
                 }
             });

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

@@ -18,7 +18,11 @@ use core_graphics::{
 };
 use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
 use font_kit::{
-    handle::Handle, hinting::HintingOptions, metrics::Metrics, source::SystemSource,
+    handle::Handle,
+    hinting::HintingOptions,
+    metrics::Metrics,
+    properties::{Style as FontkitStyle, Weight as FontkitWeight},
+    source::SystemSource,
     sources::mem::MemSource,
 };
 use parking_lot::RwLock;
@@ -187,8 +191,8 @@ impl TextSystemState {
         let idx = font_kit::matching::find_best_match(
             &candidates,
             &font_kit::properties::Properties {
-                style,
-                weight,
+                style: style.into(),
+                weight: weight.into(),
                 stretch: Default::default(),
             },
         )?;
@@ -589,6 +593,22 @@ impl From<Vector2F> for Size<f32> {
     }
 }
 
+impl From<FontWeight> for FontkitWeight {
+    fn from(value: FontWeight) -> Self {
+        FontkitWeight(value.0)
+    }
+}
+
+impl From<FontStyle> for FontkitStyle {
+    fn from(style: FontStyle) -> Self {
+        match style {
+            FontStyle::Normal => FontkitStyle::Normal,
+            FontStyle::Italic => FontkitStyle::Italic,
+            FontStyle::Oblique => FontkitStyle::Oblique,
+        }
+    }
+}
+
 // #[cfg(test)]
 // mod tests {
 //     use super::*;

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

@@ -1379,13 +1379,13 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
 }
 
 extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
-    unsafe {
-        // let window_state = get_window_state(this);
-        // let mut window_state = window_state.as_ref().borrow_mut();
-        // if let Some(scene) = window_state.scene_to_render.take() {
-        //     window_state.renderer.render(&scene);
-        // };
-    }
+    // unsafe {
+    // let window_state = get_window_state(this);
+    // let mut window_state = window_state.as_ref().borrow_mut();
+    // if let Some(scene) = window_state.scene_to_render.take() {
+    //     window_state.renderer.render(&scene);
+    // };
+    // }
 }
 
 extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {

crates/gpui3/src/scene.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{text::GlyphId, FontId};
+use crate::{FontId, GlyphId};
 
 use super::{Bounds, Hsla, Pixels, Point};
 use bytemuck::{Pod, Zeroable};

crates/gpui3/src/style.rs 🔗

@@ -3,7 +3,7 @@ use super::{
     Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, Size,
     SizeRefinement, ViewContext, WindowContext,
 };
-use crate::FontCache;
+use crate::{FontCache, TextSystem};
 use refineable::Refineable;
 pub use taffy::style::{
     AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
@@ -114,7 +114,7 @@ pub struct TextStyle {
 }
 
 impl TextStyle {
-    pub fn highlight(mut self, style: HighlightStyle, _font_cache: &FontCache) -> Result<Self> {
+    pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
         if let Some(weight) = style.font_weight {
             self.font_weight = weight;
         }

crates/gpui3/src/text_system.rs 🔗

@@ -0,0 +1,326 @@
+mod font_cache;
+mod line_wrapper;
+mod text_layout_cache;
+
+pub use font_cache::*;
+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};
+use collections::HashMap;
+use core::fmt;
+use parking_lot::Mutex;
+use std::{
+    fmt::{Debug, Display, Formatter},
+    hash::{Hash, Hasher},
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+
+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>>>,
+}
+
+impl TextSystem {
+    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+        TextSystem {
+            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()),
+        }
+    }
+
+    pub fn font_family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
+        self.font_cache.family_name(family_id)
+    }
+
+    pub fn load_font_family(
+        &self,
+        names: &[&str],
+        features: &FontFeatures,
+    ) -> Result<FontFamilyId> {
+        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()
+    }
+
+    pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
+        self.font_cache.default_font(family_id)
+    }
+
+    pub fn select_font(
+        &self,
+        family_id: FontFamilyId,
+        weight: FontWeight,
+        style: FontStyle,
+    ) -> Result<FontId> {
+        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 bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
+        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)
+    }
+
+    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        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)
+    }
+
+    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        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)
+    }
+
+    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        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)
+    }
+
+    pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        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)
+    }
+
+    pub fn layout_str<'a>(
+        &'a self,
+        text: &'a str,
+        font_size: Pixels,
+        runs: &'a [(usize, RunStyle)],
+    ) -> Line {
+        self.text_layout_cache.layout_str(text, font_size, runs)
+    }
+
+    pub fn finish_frame(&self) {
+        self.text_layout_cache.finish_frame()
+    }
+
+    pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
+        let lock = &mut self.wrapper_pool.lock();
+        let wrappers = lock.entry((font_id, font_size)).or_default();
+        let wrapper = wrappers.pop().unwrap_or_else(|| {
+            LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
+        });
+
+        LineWrapperHandle {
+            wrapper: Some(wrapper),
+            text_system: self.clone(),
+        }
+    }
+}
+
+pub struct LineWrapperHandle {
+    wrapper: Option<LineWrapper>,
+    text_system: Arc<TextSystem>,
+}
+
+impl Drop for LineWrapperHandle {
+    fn drop(&mut self) {
+        let mut state = self.text_system.wrapper_pool.lock();
+        let wrapper = self.wrapper.take().unwrap();
+        state
+            .get_mut(&(wrapper.font_id, wrapper.font_size))
+            .unwrap()
+            .push(wrapper);
+    }
+}
+
+impl Deref for LineWrapperHandle {
+    type Target = LineWrapper;
+
+    fn deref(&self) -> &Self::Target {
+        self.wrapper.as_ref().unwrap()
+    }
+}
+
+impl DerefMut for LineWrapperHandle {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.wrapper.as_mut().unwrap()
+    }
+}
+
+/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
+/// with 400.0 as normal.
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+pub struct FontWeight(pub f32);
+
+impl Default for FontWeight {
+    #[inline]
+    fn default() -> FontWeight {
+        FontWeight::NORMAL
+    }
+}
+
+impl Hash for FontWeight {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
+    }
+}
+
+impl Eq for FontWeight {}
+
+impl FontWeight {
+    /// Thin weight (100), the thinnest value.
+    pub const THIN: FontWeight = FontWeight(100.0);
+    /// Extra light weight (200).
+    pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
+    /// Light weight (300).
+    pub const LIGHT: FontWeight = FontWeight(300.0);
+    /// Normal (400).
+    pub const NORMAL: FontWeight = FontWeight(400.0);
+    /// Medium weight (500, higher than normal).
+    pub const MEDIUM: FontWeight = FontWeight(500.0);
+    /// Semibold weight (600).
+    pub const SEMIBOLD: FontWeight = FontWeight(600.0);
+    /// Bold weight (700).
+    pub const BOLD: FontWeight = FontWeight(700.0);
+    /// Extra-bold weight (800).
+    pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
+    /// Black weight (900), the thickest value.
+    pub const BLACK: FontWeight = FontWeight(900.0);
+}
+
+/// Allows italic or oblique faces to be selected.
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
+pub enum FontStyle {
+    /// A face that is neither italic not obliqued.
+    Normal,
+    /// A form that is generally cursive in nature.
+    Italic,
+    /// A typically-sloped version of the regular face.
+    Oblique,
+}
+
+impl Default for FontStyle {
+    fn default() -> FontStyle {
+        FontStyle::Normal
+    }
+}
+
+impl Display for FontStyle {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        Debug::fmt(self, f)
+    }
+}
+
+#[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,
+    pub font_id: FontId,
+    pub underline: Option<UnderlineStyle>,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct GlyphId(u32);
+
+impl From<GlyphId> for u32 {
+    fn from(value: GlyphId) -> Self {
+        value.0
+    }
+}
+
+impl From<u16> for GlyphId {
+    fn from(num: u16) -> Self {
+        GlyphId(num as u32)
+    }
+}
+
+impl From<u32> for GlyphId {
+    fn from(num: u32) -> Self {
+        GlyphId(num)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Glyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+    pub font_size: Pixels,
+    pub width: Pixels,
+    pub ascent: Pixels,
+    pub descent: Pixels,
+    pub runs: Vec<Run>,
+    pub len: usize,
+}
+
+#[derive(Debug)]
+pub struct Run {
+    pub font_id: FontId,
+    pub glyphs: Vec<Glyph>,
+}

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

@@ -1,16 +1,9 @@
-use crate::{px, Bounds, LineWrapper, Pixels, PlatformTextSystem, Result, Size};
-use anyhow::anyhow;
-pub use font_kit::properties::{
-    Properties as FontProperties, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
+use crate::{
+    px, Bounds, FontFeatures, FontStyle, FontWeight, Pixels, PlatformTextSystem, Result, Size,
 };
+use anyhow::anyhow;
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use std::{
-    collections::HashMap,
-    ops::{Deref, DerefMut},
-    sync::Arc,
-};
+use std::{collections::HashMap, sync::Arc};
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct FontFamilyId(usize);
@@ -18,73 +11,14 @@ pub struct FontFamilyId(usize);
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct FontId(pub usize);
 
-#[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>,
-}
-
-#[allow(non_camel_case_types)]
-#[derive(Deserialize)]
-enum WeightJson {
-    thin,
-    extra_light,
-    light,
-    normal,
-    medium,
-    semibold,
-    bold,
-    extra_bold,
-    black,
-}
+pub(crate) struct FontCache(RwLock<FontCacheState>);
 
-struct Family {
-    name: Arc<str>,
-    font_features: FontFeatures,
-    font_ids: Vec<FontId>,
-}
-
-pub struct FontCache(RwLock<FontCacheState>);
-
-pub struct FontCacheState {
-    font_system: Arc<dyn PlatformTextSystem>,
+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>,
-    wrapper_pool: HashMap<(FontId, Pixels), Vec<LineWrapper>>,
 }
 
 unsafe impl Send for FontCache {}
@@ -92,12 +26,11 @@ unsafe impl Send for FontCache {}
 impl FontCache {
     pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
         Self(RwLock::new(FontCacheState {
-            font_system: fonts,
+            platform_text_system: fonts,
             families: Default::default(),
             default_family: None,
             font_selections: Default::default(),
             metrics: Default::default(),
-            wrapper_pool: Default::default(),
         }))
     }
 
@@ -124,14 +57,18 @@ impl FontCache {
 
             let mut state = RwLockUpgradableReadGuard::upgrade(state);
 
-            if let Ok(font_ids) = state.font_system.load_family(name, features) {
+            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.font_system.glyph_for_char(*font_id, 'm').is_none() {
+                    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"));
                     }
                 }
@@ -162,7 +99,7 @@ impl FontCache {
                 &Default::default(),
             )
             .unwrap_or_else(|_| {
-                let all_family_names = self.0.read().font_system.all_families();
+                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())
@@ -197,7 +134,7 @@ impl FontCache {
             let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
             let family = &inner.families[family_id.0];
             let font_id = inner
-                .font_system
+                .platform_text_system
                 .select_font(&family.font_ids, weight, style)
                 .unwrap_or(family.font_ids[0]);
             inner
@@ -209,7 +146,7 @@ impl FontCache {
         }
     }
 
-    pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
+    pub fn read_metric<F, T>(&self, font_id: FontId, f: F) -> T
     where
         F: FnOnce(&FontMetrics) -> T,
         T: 'static,
@@ -218,7 +155,7 @@ impl FontCache {
         if let Some(metrics) = state.metrics.get(&font_id) {
             f(metrics)
         } else {
-            let metrics = state.font_system.font_metrics(font_id);
+            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);
@@ -227,7 +164,7 @@ impl FontCache {
     }
 
     pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
-        let bounding_box = self.metric(font_id, |m| m.bounding_box);
+        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);
@@ -239,9 +176,12 @@ impl FontCache {
         let bounds;
         {
             let state = self.0.read();
-            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
+            glyph_id = state
+                .platform_text_system
+                .glyph_for_char(font_id, 'm')
+                .unwrap();
             bounds = state
-                .font_system
+                .platform_text_system
                 .typographic_bounds(font_id, glyph_id)
                 .unwrap();
         }
@@ -253,8 +193,14 @@ impl FontCache {
         let advance;
         {
             let state = self.0.read();
-            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
-            advance = state.font_system.advance(font_id, glyph_id).unwrap();
+            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
     }
@@ -264,23 +210,23 @@ impl FontCache {
     }
 
     pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
-        self.em_size(font_id, font_size) * self.metric(font_id, |m| m.cap_height)
+        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.metric(font_id, |m| m.x_height)
+        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.metric(font_id, |m| m.ascent)
+        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.metric(font_id, |m| -m.descent)
+        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.metric(font_id, |m| m.units_per_em as f32)
+        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 {
@@ -290,49 +236,6 @@ impl FontCache {
         let padding_top = (line_height - ascent - descent) / 2.;
         padding_top + ascent
     }
-
-    pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
-        let mut state = self.0.write();
-        let wrappers = state.wrapper_pool.entry((font_id, font_size)).or_default();
-        let wrapper = wrappers
-            .pop()
-            .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
-        LineWrapperHandle {
-            wrapper: Some(wrapper),
-            font_cache: self.clone(),
-        }
-    }
-}
-
-pub struct LineWrapperHandle {
-    wrapper: Option<LineWrapper>,
-    font_cache: Arc<FontCache>,
-}
-
-impl Drop for LineWrapperHandle {
-    fn drop(&mut self) {
-        let mut state = self.font_cache.0.write();
-        let wrapper = self.wrapper.take().unwrap();
-        state
-            .wrapper_pool
-            .get_mut(&(wrapper.font_id, wrapper.font_size))
-            .unwrap()
-            .push(wrapper);
-    }
-}
-
-impl Deref for LineWrapperHandle {
-    type Target = LineWrapper;
-
-    fn deref(&self) -> &Self::Target {
-        self.wrapper.as_ref().unwrap()
-    }
-}
-
-impl DerefMut for LineWrapperHandle {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.wrapper.as_mut().unwrap()
-    }
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -348,6 +251,12 @@ pub struct FontMetrics {
     pub bounding_box: Bounds<f32>,
 }
 
+struct Family {
+    name: Arc<str>,
+    font_features: FontFeatures,
+    font_ids: Vec<FontId>,
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

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

@@ -0,0 +1,347 @@
+use super::FontId;
+use crate::{px, Line, Pixels, PlatformTextSystem, RunStyle, ShapedBoundary};
+use collections::HashMap;
+use std::{iter, sync::Arc};
+
+pub struct LineWrapper {
+    text_system: Arc<dyn PlatformTextSystem>,
+    pub(crate) font_id: FontId,
+    pub(crate) font_size: Pixels,
+    cached_ascii_char_widths: [Option<Pixels>; 128],
+    cached_other_char_widths: HashMap<char, Pixels>,
+}
+
+impl LineWrapper {
+    pub const MAX_INDENT: u32 = 256;
+
+    pub fn new(
+        font_id: FontId,
+        font_size: Pixels,
+        text_system: Arc<dyn PlatformTextSystem>,
+    ) -> Self {
+        Self {
+            text_system,
+            font_id,
+            font_size,
+            cached_ascii_char_widths: [None; 128],
+            cached_other_char_widths: HashMap::default(),
+        }
+    }
+
+    pub fn wrap_line<'a>(
+        &'a mut self,
+        line: &'a str,
+        wrap_width: Pixels,
+    ) -> impl Iterator<Item = Boundary> + 'a {
+        let mut width = px(0.);
+        let mut first_non_whitespace_ix = None;
+        let mut indent = None;
+        let mut last_candidate_ix = 0;
+        let mut last_candidate_width = px(0.);
+        let mut last_wrap_ix = 0;
+        let mut prev_c = '\0';
+        let mut char_indices = line.char_indices();
+        iter::from_fn(move || {
+            for (ix, c) in char_indices.by_ref() {
+                if c == '\n' {
+                    continue;
+                }
+
+                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                    last_candidate_ix = ix;
+                    last_candidate_width = width;
+                }
+
+                if c != ' ' && first_non_whitespace_ix.is_none() {
+                    first_non_whitespace_ix = Some(ix);
+                }
+
+                let char_width = self.width_for_char(c);
+                width += char_width;
+                if width > wrap_width && ix > last_wrap_ix {
+                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
+                    {
+                        indent = Some(
+                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
+                        );
+                    }
+
+                    if last_candidate_ix > 0 {
+                        last_wrap_ix = last_candidate_ix;
+                        width -= last_candidate_width;
+                        last_candidate_ix = 0;
+                    } else {
+                        last_wrap_ix = ix;
+                        width = char_width;
+                    }
+
+                    if let Some(indent) = indent {
+                        width += self.width_for_char(' ') * indent as f32;
+                    }
+
+                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
+                }
+                prev_c = c;
+            }
+
+            None
+        })
+    }
+
+    pub fn wrap_shaped_line<'a>(
+        &'a mut self,
+        str: &'a str,
+        line: &'a Line,
+        wrap_width: Pixels,
+    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
+        let mut first_non_whitespace_ix = None;
+        let mut last_candidate_ix = None;
+        let mut last_candidate_x = px(0.);
+        let mut last_wrap_ix = ShapedBoundary {
+            run_ix: 0,
+            glyph_ix: 0,
+        };
+        let mut last_wrap_x = px(0.);
+        let mut prev_c = '\0';
+        let mut glyphs = line
+            .runs()
+            .iter()
+            .enumerate()
+            .flat_map(move |(run_ix, run)| {
+                run.glyphs()
+                    .iter()
+                    .enumerate()
+                    .map(move |(glyph_ix, glyph)| {
+                        let character = str[glyph.index..].chars().next().unwrap();
+                        (
+                            ShapedBoundary { run_ix, glyph_ix },
+                            character,
+                            glyph.position.x,
+                        )
+                    })
+            })
+            .peekable();
+
+        iter::from_fn(move || {
+            while let Some((ix, c, x)) = glyphs.next() {
+                if c == '\n' {
+                    continue;
+                }
+
+                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                    last_candidate_ix = Some(ix);
+                    last_candidate_x = x;
+                }
+
+                if c != ' ' && first_non_whitespace_ix.is_none() {
+                    first_non_whitespace_ix = Some(ix);
+                }
+
+                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
+                let width = next_x - last_wrap_x;
+                if width > wrap_width && ix > last_wrap_ix {
+                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
+                        last_wrap_ix = last_candidate_ix;
+                        last_wrap_x = last_candidate_x;
+                    } else {
+                        last_wrap_ix = ix;
+                        last_wrap_x = x;
+                    }
+
+                    return Some(last_wrap_ix);
+                }
+                prev_c = c;
+            }
+
+            None
+        })
+    }
+
+    fn is_boundary(&self, prev: char, next: char) -> bool {
+        (prev == ' ') && (next != ' ')
+    }
+
+    #[inline(always)]
+    fn width_for_char(&mut self, c: char) -> Pixels {
+        if (c as u32) < 128 {
+            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
+                cached_width
+            } else {
+                let width = self.compute_width_for_char(c);
+                self.cached_ascii_char_widths[c as usize] = Some(width);
+                width
+            }
+        } else {
+            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
+                *cached_width
+            } else {
+                let width = self.compute_width_for_char(c);
+                self.cached_other_char_widths.insert(c, width);
+                width
+            }
+        }
+    }
+
+    fn compute_width_for_char(&self, c: char) -> Pixels {
+        self.text_system
+            .layout_line(
+                &c.to_string(),
+                self.font_size,
+                &[(
+                    1,
+                    RunStyle {
+                        font_id: self.font_id,
+                        color: Default::default(),
+                        underline: Default::default(),
+                    },
+                )],
+            )
+            .width
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Boundary {
+    pub ix: usize,
+    pub next_indent: u32,
+}
+
+impl Boundary {
+    fn new(ix: usize, next_indent: u32) -> Self {
+        Self { ix, next_indent }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{AppContext, FontWeight};
+
+    #[test]
+    fn test_wrap_line() {
+        let cx = AppContext::test();
+
+        let text_system = cx.text_system().clone();
+        let family = text_system
+            .load_font_family(&["Courier"], &Default::default())
+            .unwrap();
+        let font_id = text_system
+            .select_font(family, Default::default(), Default::default())
+            .unwrap();
+
+        let mut wrapper =
+            LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
+        assert_eq!(
+            wrapper
+                .wrap_line("aa bbb cccc ddddd eeee", px(72.))
+                .collect::<Vec<_>>(),
+            &[
+                Boundary::new(7, 0),
+                Boundary::new(12, 0),
+                Boundary::new(18, 0)
+            ],
+        );
+        assert_eq!(
+            wrapper
+                .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
+                .collect::<Vec<_>>(),
+            &[
+                Boundary::new(4, 0),
+                Boundary::new(11, 0),
+                Boundary::new(18, 0)
+            ],
+        );
+        assert_eq!(
+            wrapper
+                .wrap_line("     aaaaaaa", px(72.))
+                .collect::<Vec<_>>(),
+            &[
+                Boundary::new(7, 5),
+                Boundary::new(9, 5),
+                Boundary::new(11, 5),
+            ]
+        );
+        assert_eq!(
+            wrapper
+                .wrap_line("                            ", px(72.))
+                .collect::<Vec<_>>(),
+            &[
+                Boundary::new(7, 0),
+                Boundary::new(14, 0),
+                Boundary::new(21, 0)
+            ]
+        );
+        assert_eq!(
+            wrapper
+                .wrap_line("          aaaaaaaaaaaaaa", px(72.))
+                .collect::<Vec<_>>(),
+            &[
+                Boundary::new(7, 0),
+                Boundary::new(14, 3),
+                Boundary::new(18, 3),
+                Boundary::new(22, 3),
+            ]
+        );
+    }
+
+    // todo! repeat this test
+    #[test]
+    fn test_wrap_shaped_line() {
+        let cx = AppContext::test();
+        let text_system = cx.text_system().clone();
+
+        let family = text_system
+            .load_font_family(&["Helvetica"], &Default::default())
+            .unwrap();
+        let font_id = text_system
+            .select_font(family, Default::default(), Default::default())
+            .unwrap();
+        let normal = RunStyle {
+            font_id,
+            color: Default::default(),
+            underline: Default::default(),
+        };
+        let bold = RunStyle {
+            font_id: text_system
+                .select_font(family, FontWeight::BOLD, Default::default())
+                .unwrap(),
+            color: Default::default(),
+            underline: Default::default(),
+        };
+
+        let text = "aa bbb cccc ddddd eeee";
+        let line = text_system.layout_str(
+            text,
+            px(16.),
+            &[
+                (4, normal.clone()),
+                (5, bold.clone()),
+                (6, normal.clone()),
+                (1, bold),
+                (7, normal),
+            ],
+        );
+
+        let mut wrapper =
+            LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
+        assert_eq!(
+            wrapper
+                .wrap_shaped_line(text, &line, px(72.))
+                .collect::<Vec<_>>(),
+            &[
+                ShapedBoundary {
+                    run_ix: 1,
+                    glyph_ix: 3
+                },
+                ShapedBoundary {
+                    run_ix: 2,
+                    glyph_ix: 3
+                },
+                ShapedBoundary {
+                    run_ix: 4,
+                    glyph_ix: 2
+                }
+            ],
+        );
+    }
+}

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

@@ -1,7 +1,6 @@
-use crate::{black, px};
-
-use super::{
-    point, Bounds, FontId, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle, WindowContext,
+use crate::{
+    black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
+    Run, RunStyle, UnderlineStyle, WindowContext,
 };
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
@@ -9,52 +8,15 @@ use std::{
     borrow::Borrow,
     collections::HashMap,
     hash::{Hash, Hasher},
-    iter,
     sync::Arc,
 };
 
-pub struct TextLayoutCache {
+pub(crate) struct TextLayoutCache {
     prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
     curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
     fonts: Arc<dyn PlatformTextSystem>,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RunStyle {
-    pub color: Hsla,
-    pub font_id: FontId,
-    pub underline: Option<UnderlineStyle>,
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct GlyphId(u32);
-
-impl From<GlyphId> for u32 {
-    fn from(value: GlyphId) -> Self {
-        value.0
-    }
-}
-
-impl From<u16> for GlyphId {
-    fn from(num: u16) -> Self {
-        GlyphId(num as u32)
-    }
-}
-
-impl From<u32> for GlyphId {
-    fn from(num: u32) -> Self {
-        GlyphId(num)
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct Glyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 impl TextLayoutCache {
     pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
         Self {
@@ -207,20 +169,10 @@ struct StyleRun {
     underline: UnderlineStyle,
 }
 
-#[derive(Default, Debug)]
-pub struct LineLayout {
-    pub font_size: Pixels,
-    pub width: Pixels,
-    pub ascent: Pixels,
-    pub descent: Pixels,
-    pub runs: Vec<Run>,
-    pub len: usize,
-}
-
-#[derive(Debug)]
-pub struct Run {
-    pub font_id: FontId,
-    pub glyphs: Vec<Glyph>,
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ShapedBoundary {
+    pub run_ix: usize,
+    pub glyph_ix: usize,
 }
 
 impl Line {
@@ -311,7 +263,7 @@ impl Line {
 
         for run in &self.layout.runs {
             let max_glyph_width = cx
-                .font_cache()
+                .text_system()
                 .bounding_box(run.font_id, self.layout.font_size)
                 .width;
 
@@ -485,7 +437,7 @@ impl Line {
                 let _glyph_bounds = Bounds {
                     origin: glyph_origin,
                     size: cx
-                        .font_cache()
+                        .text_system()
                         .bounding_box(run.font_id, self.layout.font_size),
                 };
                 // todo!()
@@ -528,353 +480,3 @@ impl Run {
         &self.glyphs
     }
 }
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct Boundary {
-    pub ix: usize,
-    pub next_indent: u32,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
-    pub run_ix: usize,
-    pub glyph_ix: usize,
-}
-
-impl Boundary {
-    fn new(ix: usize, next_indent: u32) -> Self {
-        Self { ix, next_indent }
-    }
-}
-
-pub struct LineWrapper {
-    font_system: Arc<dyn PlatformTextSystem>,
-    pub(crate) font_id: FontId,
-    pub(crate) font_size: Pixels,
-    cached_ascii_char_widths: [Option<Pixels>; 128],
-    cached_other_char_widths: HashMap<char, Pixels>,
-}
-
-impl LineWrapper {
-    pub const MAX_INDENT: u32 = 256;
-
-    pub fn new(
-        font_id: FontId,
-        font_size: Pixels,
-        font_system: Arc<dyn PlatformTextSystem>,
-    ) -> Self {
-        Self {
-            font_system,
-            font_id,
-            font_size,
-            cached_ascii_char_widths: [None; 128],
-            cached_other_char_widths: HashMap::new(),
-        }
-    }
-
-    pub fn wrap_line<'a>(
-        &'a mut self,
-        line: &'a str,
-        wrap_width: Pixels,
-    ) -> impl Iterator<Item = Boundary> + 'a {
-        let mut width = px(0.);
-        let mut first_non_whitespace_ix = None;
-        let mut indent = None;
-        let mut last_candidate_ix = 0;
-        let mut last_candidate_width = px(0.);
-        let mut last_wrap_ix = 0;
-        let mut prev_c = '\0';
-        let mut char_indices = line.char_indices();
-        iter::from_fn(move || {
-            for (ix, c) in char_indices.by_ref() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = ix;
-                    last_candidate_width = width;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let char_width = self.width_for_char(c);
-                width += char_width;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
-                    {
-                        indent = Some(
-                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
-                        );
-                    }
-
-                    if last_candidate_ix > 0 {
-                        last_wrap_ix = last_candidate_ix;
-                        width -= last_candidate_width;
-                        last_candidate_ix = 0;
-                    } else {
-                        last_wrap_ix = ix;
-                        width = char_width;
-                    }
-
-                    if let Some(indent) = indent {
-                        width += self.width_for_char(' ') * indent as f32;
-                    }
-
-                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    pub fn wrap_shaped_line<'a>(
-        &'a mut self,
-        str: &'a str,
-        line: &'a Line,
-        wrap_width: Pixels,
-    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
-        let mut first_non_whitespace_ix = None;
-        let mut last_candidate_ix = None;
-        let mut last_candidate_x = px(0.);
-        let mut last_wrap_ix = ShapedBoundary {
-            run_ix: 0,
-            glyph_ix: 0,
-        };
-        let mut last_wrap_x = px(0.);
-        let mut prev_c = '\0';
-        let mut glyphs = line
-            .runs()
-            .iter()
-            .enumerate()
-            .flat_map(move |(run_ix, run)| {
-                run.glyphs()
-                    .iter()
-                    .enumerate()
-                    .map(move |(glyph_ix, glyph)| {
-                        let character = str[glyph.index..].chars().next().unwrap();
-                        (
-                            ShapedBoundary { run_ix, glyph_ix },
-                            character,
-                            glyph.position.x,
-                        )
-                    })
-            })
-            .peekable();
-
-        iter::from_fn(move || {
-            while let Some((ix, c, x)) = glyphs.next() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = Some(ix);
-                    last_candidate_x = x;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
-                let width = next_x - last_wrap_x;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
-                        last_wrap_ix = last_candidate_ix;
-                        last_wrap_x = last_candidate_x;
-                    } else {
-                        last_wrap_ix = ix;
-                        last_wrap_x = x;
-                    }
-
-                    return Some(last_wrap_ix);
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    fn is_boundary(&self, prev: char, next: char) -> bool {
-        (prev == ' ') && (next != ' ')
-    }
-
-    #[inline(always)]
-    fn width_for_char(&mut self, c: char) -> Pixels {
-        if (c as u32) < 128 {
-            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
-                cached_width
-            } else {
-                let width = self.compute_width_for_char(c);
-                self.cached_ascii_char_widths[c as usize] = Some(width);
-                width
-            }
-        } else {
-            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
-                *cached_width
-            } else {
-                let width = self.compute_width_for_char(c);
-                self.cached_other_char_widths.insert(c, width);
-                width
-            }
-        }
-    }
-
-    fn compute_width_for_char(&self, c: char) -> Pixels {
-        self.font_system
-            .layout_line(
-                &c.to_string(),
-                self.font_size,
-                &[(
-                    1,
-                    RunStyle {
-                        font_id: self.font_id,
-                        color: Default::default(),
-                        underline: Default::default(),
-                    },
-                )],
-            )
-            .width
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::{AppContext, FontWeight};
-
-    #[test]
-    fn test_wrap_line() {
-        let cx = AppContext::test();
-
-        let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().text_system();
-        let family = font_cache
-            .load_family(&["Courier"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family, Default::default(), Default::default())
-            .unwrap();
-
-        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
-        assert_eq!(
-            wrapper
-                .wrap_line("aa bbb cccc ddddd eeee", px(72.))
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(12, 0),
-                Boundary::new(18, 0)
-            ],
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(4, 0),
-                Boundary::new(11, 0),
-                Boundary::new(18, 0)
-            ],
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("     aaaaaaa", px(72.))
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 5),
-                Boundary::new(9, 5),
-                Boundary::new(11, 5),
-            ]
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("                            ", px(72.))
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(14, 0),
-                Boundary::new(21, 0)
-            ]
-        );
-        assert_eq!(
-            wrapper
-                .wrap_line("          aaaaaaaaaaaaaa", px(72.))
-                .collect::<Vec<_>>(),
-            &[
-                Boundary::new(7, 0),
-                Boundary::new(14, 3),
-                Boundary::new(18, 3),
-                Boundary::new(22, 3),
-            ]
-        );
-    }
-
-    // todo! repeat this test
-    #[test]
-    fn test_wrap_shaped_line() {
-        let cx = AppContext::test();
-        let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().text_system();
-        let text_layout_cache = TextLayoutCache::new(font_system.clone());
-
-        let family = font_cache
-            .load_family(&["Helvetica"], &Default::default())
-            .unwrap();
-        let font_id = font_cache
-            .select_font(family, Default::default(), Default::default())
-            .unwrap();
-        let normal = RunStyle {
-            font_id,
-            color: Default::default(),
-            underline: Default::default(),
-        };
-        let bold = RunStyle {
-            font_id: font_cache
-                .select_font(family, FontWeight::BOLD, Default::default())
-                .unwrap(),
-            color: Default::default(),
-            underline: Default::default(),
-        };
-
-        let text = "aa bbb cccc ddddd eeee";
-        let line = text_layout_cache.layout_str(
-            text,
-            px(16.),
-            &[
-                (4, normal.clone()),
-                (5, bold.clone()),
-                (6, normal.clone()),
-                (1, bold),
-                (7, normal),
-            ],
-        );
-
-        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
-        assert_eq!(
-            wrapper
-                .wrap_shaped_line(text, &line, px(72.))
-                .collect::<Vec<_>>(),
-            &[
-                ShapedBoundary {
-                    run_ix: 1,
-                    glyph_ix: 3
-                },
-                ShapedBoundary {
-                    run_ix: 2,
-                    glyph_ix: 3
-                },
-                ShapedBoundary {
-                    run_ix: 4,
-                    glyph_ix: 2
-                }
-            ],
-        );
-    }
-}

crates/gpui3/src/window.rs 🔗

@@ -1,7 +1,7 @@
-use crate::{PlatformWindow, Point, TextStyleRefinement};
+use crate::{PlatformWindow, Point, Style, TextStyleRefinement};
 
 use super::{
-    px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
+    px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference,
     TaffyLayoutEngine,
 };
 use anyhow::Result;