diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 6423e44ae0..2c5738ad96 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -74,12 +74,13 @@ impl Element for Text { let rem_size = cx.rem_size(); let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let element_state = element_state.clone(); - move |_, _| { + move |known_dimensions, _| { let Some(line_layout) = text_system - .layout_line( + .layout_text( text.as_ref(), font_size, &[(text.len(), text_style.to_run())], + known_dimensions.width, // Wrap if we know the width. ) .log_err() else { diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 6d9cf3ea75..7caf192404 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -6,8 +6,8 @@ mod test; use crate::{ AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, - GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, - Result, Scene, ShapedLine, SharedString, Size, + GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, + RenderSvgParams, Result, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -171,7 +171,7 @@ pub trait PlatformTextSystem: Send + Sync { fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option; fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result>; fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size, Vec)>; - fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine; + fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout; fn wrap_line( &self, text: &str, diff --git a/crates/gpui3/src/platform/mac/text_system.rs b/crates/gpui3/src/platform/mac/text_system.rs index 0320f91386..659be155f0 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/crates/gpui3/src/platform/mac/text_system.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, - FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, - ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, + FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, + ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, }; use anyhow::anyhow; use cocoa::appkit::{CGFloat, CGPoint}; @@ -154,7 +154,7 @@ impl PlatformTextSystem for MacTextSystem { text: &str, font_size: Pixels, font_runs: &[(usize, FontId)], - ) -> ShapedLine { + ) -> LineLayout { self.0.write().layout_line(text, font_size, font_runs) } @@ -342,7 +342,7 @@ impl MacTextSystemState { text: &str, font_size: Pixels, font_runs: &[(usize, FontId)], - ) -> ShapedLine { + ) -> LineLayout { // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. let mut string = CFMutableAttributedString::new(); { @@ -394,7 +394,7 @@ impl MacTextSystemState { let font_id = self.id_for_native_font(font); let mut ix_converter = StringIndexConverter::new(text); - let mut glyphs = Vec::new(); + let mut glyphs = SmallVec::new(); for ((glyph_id, position), glyph_utf16_ix) in run .glyphs() .iter() @@ -415,7 +415,7 @@ impl MacTextSystemState { } let typographic_bounds = line.get_typographic_bounds(); - ShapedLine { + LineLayout { width: typographic_bounds.width.into(), ascent: typographic_bounds.ascent.into(), descent: typographic_bounds.descent.into(), diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index 4ac98556b4..514d12cd83 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -7,6 +7,7 @@ use anyhow::anyhow; pub use font_features::*; pub use line::*; use line_wrapper::*; +use smallvec::SmallVec; pub use text_layout_cache::*; use crate::{ @@ -143,13 +144,15 @@ impl TextSystem { } } - pub fn layout_line( + pub fn layout_text( &self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)], - ) -> Result { - let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); + wrap_width: Option, + ) -> Result> { + let mut font_runs: Vec<(usize, FontId)> = + self.font_runs_pool.lock().pop().unwrap_or_default(); let mut last_font: Option<&Font> = None; for (len, style) in runs { @@ -163,14 +166,47 @@ impl TextSystem { font_runs.push((*len, self.font_id(&style.font)?)); } - let layout = self - .text_layout_cache - .layout_line(text, font_size, &font_runs); + let mut layouts = SmallVec::new(); + let mut start = 0; + let mut run_start = 0; + for line in text.lines() { + let end = start + line.len(); + let mut run_end = run_start; + let mut line_length = 0; + for (len, _) in font_runs[run_start..].iter() { + line_length += len; + if *len >= line_length { + break; + } + run_end += 1; + } + // If a run lands in the middle of a line, create an additional run for the remaining characters. + if line_length < end - start { + // Create a run for the part that fits this line. + let partial_run = font_runs[run_end]; + partial_run.0 = line_length; + layouts.push(self.text_layout_cache.layout_line( + &text[start..start + line_length], + font_size, + &font_runs[run_start..=run_end], + )); + // Update the original run to only include the part that does not fit this line. + font_runs[run_end].0 -= line_length; + } else { + layouts.push(self.text_layout_cache.layout_line( + &text[start..end], + font_size, + &font_runs[run_start..run_end], + )); + run_start = run_end; + } + start = end + 1; + } font_runs.clear(); self.font_runs_pool.lock().push(font_runs); - Ok(Line::new(layout.clone(), runs)) + Ok(layouts) } pub fn end_frame(&self) { @@ -346,7 +382,7 @@ impl From for GlyphId { } #[derive(Default, Debug)] -pub struct ShapedLine { +pub struct LineLayout { pub font_size: Pixels, pub width: Pixels, pub ascent: Pixels, @@ -358,7 +394,7 @@ pub struct ShapedLine { #[derive(Debug)] pub struct ShapedRun { pub font_id: FontId, - pub glyphs: Vec, + pub glyphs: SmallVec<[ShapedGlyph; 8]>, } #[derive(Clone, Debug)] diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index c63c18825b..5064046c4f 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -1,5 +1,5 @@ use crate::{ - black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine, + black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, RunStyle, ShapedBoundary, ShapedRun, UnderlineStyle, WindowContext, }; use anyhow::Result; @@ -8,7 +8,7 @@ use std::sync::Arc; #[derive(Default, Debug, Clone)] pub struct Line { - layout: Arc, + layout: Arc, style_runs: SmallVec<[StyleRun; 32]>, } @@ -20,7 +20,7 @@ struct StyleRun { } impl Line { - pub fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { + pub fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { let mut style_runs = SmallVec::new(); for (len, style) in runs { style_runs.push(StyleRun { diff --git a/crates/gpui3/src/text_system/line_wrapper.rs b/crates/gpui3/src/text_system/line_wrapper.rs index 87c75cc9ac..4c0214ce80 100644 --- a/crates/gpui3/src/text_system/line_wrapper.rs +++ b/crates/gpui3/src/text_system/line_wrapper.rs @@ -287,7 +287,7 @@ mod tests { let text = "aa bbb cccc ddddd eeee"; let line = text_system - .layout_line( + .layout_text( text, px(16.), &[ @@ -297,6 +297,7 @@ mod tests { (1, bold.clone()), (7, normal.clone()), ], + None, ) .unwrap(); diff --git a/crates/gpui3/src/text_system/text_layout_cache.rs b/crates/gpui3/src/text_system/text_layout_cache.rs index e9768aaf21..1c3432aea2 100644 --- a/crates/gpui3/src/text_system/text_layout_cache.rs +++ b/crates/gpui3/src/text_system/text_layout_cache.rs @@ -1,4 +1,4 @@ -use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun}; +use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -9,8 +9,8 @@ use std::{ }; pub(crate) struct TextLayoutCache { - prev_frame: Mutex>>, - curr_frame: RwLock>>, + prev_frame: Mutex>>, + curr_frame: RwLock>>, platform_text_system: Arc, } @@ -35,7 +35,7 @@ impl TextLayoutCache { text: &'a str, font_size: Pixels, runs: &[(usize, FontId)], - ) -> Arc { + ) -> Arc { let key = &CacheKeyRef { text, font_size,