diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 2f85b23fc9260ba2ba5b391d05e8d7abfceeab17..656f2e44750641d752fce2ff4f639cce59601c25 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/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, - fonts: Arc, + fonts: Arc, 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") }, diff --git a/crates/gpui3/Cargo.toml b/crates/gpui3/Cargo.toml index 339b5b76fd53321fdfba8df4b78f2fd656a71781..3a995aeb40adedaa254b77fe7e9be176c8bfb696 100644 --- a/crates/gpui3/Cargo.toml +++ b/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" diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 8ce158f106f300160cae9d1e263adad1abf810eb..fb0c3f55bd7ce558b811dbd545d64b9a5a188596 100644 --- a/crates/gpui3/src/app.rs +++ b/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>); impl App { - pub fn new(platform: Rc) -> 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(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, - font_cache: Arc, + text_system: Arc, pub(crate) entities: SlotMap>>, pub(crate) windows: SlotMap>, // We recycle this memory across layout requests. @@ -26,10 +37,10 @@ pub struct AppContext { impl AppContext { pub fn new(platform: Rc) -> 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 { - &self.font_cache + pub fn text_system(&self) -> &Arc { + &self.text_system } pub fn open_window( diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index 77e323204b8d35a3862df29bc2f29a739d31ae2b..4545bc83bd7e0e9716db3935e66126472a7a1961 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -47,7 +47,7 @@ impl Element for Div { cx.pop_text_style(); } - Ok((cx.request_layout(style, children.clone())?, children)) + Ok((cx.request_layout(style.into(), children.clone())?, children)) } fn paint( diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 2c665cc3345e45ec2d57a9b8f5ccc09b0b0cf8f2..6c92c6d85654e30ab90b67ac9477401b78f0657f 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/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::*; diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 93a8f577bdb65a24775bd6c350de25dd52f05dc3..f75f806dc0bf7c6df16004e2c47c522a1046e541 100644 --- a/crates/gpui3/src/platform.rs +++ b/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 { -// MacPlatform -// } +#[cfg(target_os = "macos")] +pub(crate) fn current_platform() -> Rc { + Rc::new(MacPlatform::new()) +} pub trait Platform { fn executor(&self) -> Rc; diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index 490301eafd09d8ffa6df58a89a2d8710398e9d89..25a9e035832449714fca40bc0066f60bbd745bb9 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/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); } }); diff --git a/crates/gpui3/src/platform/mac/text_system.rs b/crates/gpui3/src/platform/mac/text_system.rs index d572b4461a5c8e95536a1f1b451e54993e7c4039..5d87b77a6bc300b220ab2fc6d539d5f587f58d85 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/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 for Size { } } +impl From for FontkitWeight { + fn from(value: FontWeight) -> Self { + FontkitWeight(value.0) + } +} + +impl From 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::*; diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index ae7ac3df61b8d29ab4dff79587274913a64c6076..56d74ee962d0ebed5cad5aab3d2f05c8d2151a66 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/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 { diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index b25322a5e08ed43fffef239c085113ef5bc6235d..ea39f232188110697f8c17cc8e2877cba143a3f8 100644 --- a/crates/gpui3/src/scene.rs +++ b/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}; diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index fbd7d5fcea3f6fd2a020867e68e87f8c3de9da89..176f5cda043330a6aa5b22134341e64c022a76c0 100644 --- a/crates/gpui3/src/style.rs +++ b/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 { + pub fn highlight(mut self, style: HighlightStyle) -> Result { if let Some(weight) = style.font_weight { self.font_weight = weight; } diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..c7935801ccce13bfc6568a48d967fdc3917833a0 --- /dev/null +++ b/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, + text_layout_cache: Arc, + platform_text_system: Arc, + wrapper_pool: Mutex>>, +} + +impl TextSystem { + pub fn new(platform_text_system: Arc) -> 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> { + self.font_cache.family_name(family_id) + } + + pub fn load_font_family( + &self, + names: &[&str], + features: &FontFeatures, + ) -> Result { + 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 { + self.font_cache.select_font(family_id, weight, style) + } + + pub fn read_font_metric(&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 { + 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, 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, + text_system: Arc, +} + +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(&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, + pub case: Option, + pub cpsp: Option, + pub frac: Option, + pub liga: Option, + pub onum: Option, + pub ordn: Option, + pub pnum: Option, + pub ss01: Option, + pub ss02: Option, + pub ss03: Option, + pub ss04: Option, + pub ss05: Option, + pub ss06: Option, + pub ss07: Option, + pub ss08: Option, + pub ss09: Option, + pub ss10: Option, + pub ss11: Option, + pub ss12: Option, + pub ss13: Option, + pub ss14: Option, + pub ss15: Option, + pub ss16: Option, + pub ss17: Option, + pub ss18: Option, + pub ss19: Option, + pub ss20: Option, + pub subs: Option, + pub sups: Option, + pub swsh: Option, + pub titl: Option, + pub tnum: Option, + pub zero: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RunStyle { + pub color: Hsla, + pub font_id: FontId, + pub underline: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct GlyphId(u32); + +impl From for u32 { + fn from(value: GlyphId) -> Self { + value.0 + } +} + +impl From for GlyphId { + fn from(num: u16) -> Self { + GlyphId(num as u32) + } +} + +impl From for GlyphId { + fn from(num: u32) -> Self { + GlyphId(num) + } +} + +#[derive(Clone, Debug)] +pub struct Glyph { + pub id: GlyphId, + pub position: Point, + 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, + pub len: usize, +} + +#[derive(Debug)] +pub struct Run { + pub font_id: FontId, + pub glyphs: Vec, +} diff --git a/crates/gpui3/src/fonts.rs b/crates/gpui3/src/text_system/font_cache.rs similarity index 65% rename from crates/gpui3/src/fonts.rs rename to crates/gpui3/src/text_system/font_cache.rs index 9a3c579f8c1bd67e09d0158686bb3febf1377a7b..127b4cc94e8ae7274385fcf2e3b5b999e9660bca 100644 --- a/crates/gpui3/src/fonts.rs +++ b/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, - pub case: Option, - pub cpsp: Option, - pub frac: Option, - pub liga: Option, - pub onum: Option, - pub ordn: Option, - pub pnum: Option, - pub ss01: Option, - pub ss02: Option, - pub ss03: Option, - pub ss04: Option, - pub ss05: Option, - pub ss06: Option, - pub ss07: Option, - pub ss08: Option, - pub ss09: Option, - pub ss10: Option, - pub ss11: Option, - pub ss12: Option, - pub ss13: Option, - pub ss14: Option, - pub ss15: Option, - pub ss16: Option, - pub ss17: Option, - pub ss18: Option, - pub ss19: Option, - pub ss20: Option, - pub subs: Option, - pub sups: Option, - pub swsh: Option, - pub titl: Option, - pub tnum: Option, - pub zero: Option, -} - -#[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); -struct Family { - name: Arc, - font_features: FontFeatures, - font_ids: Vec, -} - -pub struct FontCache(RwLock); - -pub struct FontCacheState { - font_system: Arc, +pub(crate) struct FontCacheState { + platform_text_system: Arc, families: Vec, default_family: Option, font_selections: HashMap>, metrics: HashMap, - wrapper_pool: HashMap<(FontId, Pixels), Vec>, } unsafe impl Send for FontCache {} @@ -92,12 +26,11 @@ unsafe impl Send for FontCache {} impl FontCache { pub fn new(fonts: Arc) -> 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(&self, font_id: FontId, f: F) -> T + pub fn read_metric(&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 { - 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, 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, - font_cache: Arc, -} - -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, } +struct Family { + name: Arc, + font_features: FontFeatures, + font_ids: Vec, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ade6aeda0e7ce6b6e438a84da6061030d2bd738 --- /dev/null +++ b/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, + pub(crate) font_id: FontId, + pub(crate) font_size: Pixels, + cached_ascii_char_widths: [Option; 128], + cached_other_char_widths: HashMap, +} + +impl LineWrapper { + pub const MAX_INDENT: u32 = 256; + + pub fn new( + font_id: FontId, + font_size: Pixels, + text_system: Arc, + ) -> 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 + '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 + '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::>(), + &[ + Boundary::new(7, 0), + Boundary::new(12, 0), + Boundary::new(18, 0) + ], + ); + assert_eq!( + wrapper + .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0)) + .collect::>(), + &[ + Boundary::new(4, 0), + Boundary::new(11, 0), + Boundary::new(18, 0) + ], + ); + assert_eq!( + wrapper + .wrap_line(" aaaaaaa", px(72.)) + .collect::>(), + &[ + Boundary::new(7, 5), + Boundary::new(9, 5), + Boundary::new(11, 5), + ] + ); + assert_eq!( + wrapper + .wrap_line(" ", px(72.)) + .collect::>(), + &[ + Boundary::new(7, 0), + Boundary::new(14, 0), + Boundary::new(21, 0) + ] + ); + assert_eq!( + wrapper + .wrap_line(" aaaaaaaaaaaaaa", px(72.)) + .collect::>(), + &[ + 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::>(), + &[ + ShapedBoundary { + run_ix: 1, + glyph_ix: 3 + }, + ShapedBoundary { + run_ix: 2, + glyph_ix: 3 + }, + ShapedBoundary { + run_ix: 4, + glyph_ix: 2 + } + ], + ); + } +} diff --git a/crates/gpui3/src/text.rs b/crates/gpui3/src/text_system/text_layout_cache.rs similarity index 57% rename from crates/gpui3/src/text.rs rename to crates/gpui3/src/text_system/text_layout_cache.rs index c088cdfd68e1e5f7d94151e79a0835b681d09e18..0c5a383e681880ce81b4513e9edd2fd9a819e19b 100644 --- a/crates/gpui3/src/text.rs +++ b/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>>, curr_frame: RwLock>>, fonts: Arc, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RunStyle { - pub color: Hsla, - pub font_id: FontId, - pub underline: Option, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct GlyphId(u32); - -impl From for u32 { - fn from(value: GlyphId) -> Self { - value.0 - } -} - -impl From for GlyphId { - fn from(num: u16) -> Self { - GlyphId(num as u32) - } -} - -impl From for GlyphId { - fn from(num: u32) -> Self { - GlyphId(num) - } -} - -#[derive(Clone, Debug)] -pub struct Glyph { - pub id: GlyphId, - pub position: Point, - pub index: usize, - pub is_emoji: bool, -} - impl TextLayoutCache { pub fn new(fonts: Arc) -> 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, - pub len: usize, -} - -#[derive(Debug)] -pub struct Run { - pub font_id: FontId, - pub glyphs: Vec, +#[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, - pub(crate) font_id: FontId, - pub(crate) font_size: Pixels, - cached_ascii_char_widths: [Option; 128], - cached_other_char_widths: HashMap, -} - -impl LineWrapper { - pub const MAX_INDENT: u32 = 256; - - pub fn new( - font_id: FontId, - font_size: Pixels, - font_system: Arc, - ) -> 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 + '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 + '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::>(), - &[ - Boundary::new(7, 0), - Boundary::new(12, 0), - Boundary::new(18, 0) - ], - ); - assert_eq!( - wrapper - .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0)) - .collect::>(), - &[ - Boundary::new(4, 0), - Boundary::new(11, 0), - Boundary::new(18, 0) - ], - ); - assert_eq!( - wrapper - .wrap_line(" aaaaaaa", px(72.)) - .collect::>(), - &[ - Boundary::new(7, 5), - Boundary::new(9, 5), - Boundary::new(11, 5), - ] - ); - assert_eq!( - wrapper - .wrap_line(" ", px(72.)) - .collect::>(), - &[ - Boundary::new(7, 0), - Boundary::new(14, 0), - Boundary::new(21, 0) - ] - ); - assert_eq!( - wrapper - .wrap_line(" aaaaaaaaaaaaaa", px(72.)) - .collect::>(), - &[ - 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::>(), - &[ - ShapedBoundary { - run_ix: 1, - glyph_ix: 3 - }, - ShapedBoundary { - run_ix: 2, - glyph_ix: 3 - }, - ShapedBoundary { - run_ix: 4, - glyph_ix: 2 - } - ], - ); - } -} diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index b0da95b49a17b07e9401868ed748abc7b13f5aa0..0052d09b32d8f21bec0ade4ceb0bedbd0f410bed 100644 --- a/crates/gpui3/src/window.rs +++ b/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;