WIP

Nathan Sobo created

Change summary

crates/gpui3/src/elements/text.rs                 |  5 
crates/gpui3/src/platform.rs                      |  6 
crates/gpui3/src/platform/mac/text_system.rs      | 12 +-
crates/gpui3/src/text_system.rs                   | 54 ++++++++++++++--
crates/gpui3/src/text_system/line.rs              |  6 
crates/gpui3/src/text_system/line_wrapper.rs      |  3 
crates/gpui3/src/text_system/text_layout_cache.rs |  8 +-
7 files changed, 66 insertions(+), 28 deletions(-)

Detailed changes

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

@@ -74,12 +74,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
         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 {

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<GlyphId>;
     fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
     fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
-    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,

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(),

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<Line> {
-        let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
+        wrap_width: Option<Pixels>,
+    ) -> Result<SmallVec<[Line; 1]>> {
+        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<u32> 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<ShapedGlyph>,
+    pub glyphs: SmallVec<[ShapedGlyph; 8]>,
 }
 
 #[derive(Clone, Debug)]

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<ShapedLine>,
+    layout: Arc<LineLayout>,
     style_runs: SmallVec<[StyleRun; 32]>,
 }
 
@@ -20,7 +20,7 @@ struct StyleRun {
 }
 
 impl Line {
-    pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
+    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
         let mut style_runs = SmallVec::new();
         for (len, style) in runs {
             style_runs.push(StyleRun {

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();
 

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<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
+    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
 }
 
@@ -35,7 +35,7 @@ impl TextLayoutCache {
         text: &'a str,
         font_size: Pixels,
         runs: &[(usize, FontId)],
-    ) -> Arc<ShapedLine> {
+    ) -> Arc<LineLayout> {
         let key = &CacheKeyRef {
             text,
             font_size,