Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/geometry.rs                    | 79 ++++++++++++++++
crates/gpui3/src/platform.rs                    | 12 +
crates/gpui3/src/platform/mac/metal_renderer.rs |  8 
crates/gpui3/src/platform/mac/text_system.rs    | 91 ++++++++++++------
crates/gpui3/src/platform/mac/window.rs         | 12 +-
crates/gpui3/src/scene.rs                       | 10 +-
crates/gpui3/src/text_system.rs                 | 12 +
crates/gpui3/src/text_system/line.rs            | 43 ++------
crates/gpui3/src/window.rs                      | 62 +++++++++++-
9 files changed, 235 insertions(+), 94 deletions(-)

Detailed changes

crates/gpui3/src/geometry.rs 🔗

@@ -618,6 +618,12 @@ impl From<ScaledPixels> for DevicePixels {
     }
 }
 
+impl From<DevicePixels> for ScaledPixels {
+    fn from(device: DevicePixels) -> Self {
+        ScaledPixels(device.0 as f32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
 pub struct Rems(f32);
 
@@ -802,3 +808,76 @@ impl From<()> for Length {
         Self::Definite(DefiniteLength::default())
     }
 }
+
+pub trait IsZero {
+    fn is_zero(&self) -> bool;
+}
+
+impl IsZero for DevicePixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0
+    }
+}
+
+impl IsZero for ScaledPixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for Pixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for Rems {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for AbsoluteLength {
+    fn is_zero(&self) -> bool {
+        match self {
+            AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
+            AbsoluteLength::Rems(rems) => rems.is_zero(),
+        }
+    }
+}
+
+impl IsZero for DefiniteLength {
+    fn is_zero(&self) -> bool {
+        match self {
+            DefiniteLength::Absolute(length) => length.is_zero(),
+            DefiniteLength::Fraction(fraction) => *fraction == 0.,
+        }
+    }
+}
+
+impl IsZero for Length {
+    fn is_zero(&self) -> bool {
+        match self {
+            Length::Definite(length) => length.is_zero(),
+            Length::Auto => false,
+        }
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Point<T> {
+    fn is_zero(&self) -> bool {
+        self.x.is_zero() && self.y.is_zero()
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Size<T> {
+    fn is_zero(&self) -> bool {
+        self.width.is_zero() && self.height.is_zero()
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Bounds<T> {
+    fn is_zero(&self) -> bool {
+        self.origin.is_zero() && self.size.is_zero()
+    }
+}

crates/gpui3/src/platform.rs 🔗

@@ -6,8 +6,8 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
-    RasterizeGlyphParams, Result, Scene, ShapedLine, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId,
+    GlyphRasterizationParams, Pixels, Point, Result, Scene, ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -147,7 +147,7 @@ pub trait PlatformWindow {
     fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
     fn draw(&self, scene: Scene);
 
-    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizeGlyphParams>>;
+    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<GlyphRasterizationParams>>;
 }
 
 pub trait PlatformDispatcher: Send + Sync {
@@ -163,9 +163,13 @@ pub trait PlatformTextSystem: Send + Sync {
     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
+    fn glyph_raster_bounds(
+        &self,
+        params: &GlyphRasterizationParams,
+    ) -> Result<Bounds<DevicePixels>>;
     fn rasterize_glyph(
         &self,
-        glyph_id: &RasterizeGlyphParams,
+        params: &GlyphRasterizationParams,
     ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
     fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
     fn wrap_line(

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

@@ -1,6 +1,6 @@
 use crate::{
-    point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, Quad,
-    RasterizeGlyphParams, Scene, Size,
+    point, size, AtlasTextureId, DevicePixels, GlyphRasterizationParams, MetalAtlas,
+    MonochromeSprite, Quad, Scene, Size,
 };
 use cocoa::{
     base::{NO, YES},
@@ -22,7 +22,7 @@ pub struct MetalRenderer {
     sprites_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
-    glyph_atlas: Arc<MetalAtlas<RasterizeGlyphParams>>,
+    glyph_atlas: Arc<MetalAtlas<GlyphRasterizationParams>>,
 }
 
 impl MetalRenderer {
@@ -126,7 +126,7 @@ impl MetalRenderer {
         &*self.layer
     }
 
-    pub fn glyph_atlas(&self) -> &Arc<MetalAtlas<RasterizeGlyphParams>> {
+    pub fn glyph_atlas(&self) -> &Arc<MetalAtlas<GlyphRasterizationParams>> {
         &self.glyph_atlas
     }
 

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

@@ -1,6 +1,6 @@
 use crate::{
     point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
-    FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RasterizeGlyphParams, Result,
+    FontWeight, GlyphId, GlyphRasterizationParams, Pixels, PlatformTextSystem, Point, Result,
     ShapedGlyph, ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
 };
 use anyhow::anyhow;
@@ -134,9 +134,16 @@ impl PlatformTextSystem for MacTextSystem {
         self.0.read().glyph_for_char(font_id, ch)
     }
 
+    fn glyph_raster_bounds(
+        &self,
+        params: &GlyphRasterizationParams,
+    ) -> Result<Bounds<DevicePixels>> {
+        self.0.read().raster_bounds(params)
+    }
+
     fn rasterize_glyph(
         &self,
-        glyph_id: &RasterizeGlyphParams,
+        glyph_id: &GlyphRasterizationParams,
     ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
         self.0.read().rasterize_glyph(glyph_id)
     }
@@ -230,37 +237,54 @@ impl MacTextSystemState {
             })
     }
 
+    fn raster_bounds(&self, params: &GlyphRasterizationParams) -> Result<Bounds<DevicePixels>> {
+        let font = &self.fonts[params.font_id.0];
+        let scale = Transform2F::from_scale(params.scale_factor);
+        Ok(font
+            .raster_bounds(
+                params.glyph_id.into(),
+                params.font_size.into(),
+                scale,
+                HintingOptions::None,
+                font_kit::canvas::RasterizationOptions::GrayscaleAa,
+            )?
+            .into())
+    }
+
     fn rasterize_glyph(
         &self,
-        glyph_id: &RasterizeGlyphParams,
+        params: &GlyphRasterizationParams,
     ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
-        let font = &self.fonts[glyph_id.font_id.0];
-        let scale = Transform2F::from_scale(glyph_id.scale_factor);
-        let glyph_bounds = font.raster_bounds(
-            glyph_id.glyph_id.into(),
-            glyph_id.font_size.into(),
-            scale,
-            HintingOptions::None,
-            font_kit::canvas::RasterizationOptions::GrayscaleAa,
-        )?;
-
-        if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
+        let glyph_bounds = self.raster_bounds(params)?;
+
+        // let scale = Transform2F::from_scale(params.scale_factor);
+        // let glyph_bounds = font.raster_bounds(
+        //     params.glyph_id.into(),
+        //     params.font_size.into(),
+        //     scale,
+        //     HintingOptions::None,
+        //     font_kit::canvas::RasterizationOptions::GrayscaleAa,
+        // )?;
+
+        if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
             Err(anyhow!("glyph bounds are empty"))
         } else {
-            // Make room for subpixel variants.
-            let subpixel_padding = Vector2I::new(
-                glyph_id.subpixel_variant.x.min(1) as i32,
-                glyph_id.subpixel_variant.y.min(1) as i32,
-            );
-            let bitmap_size = glyph_bounds.size() + subpixel_padding;
+            // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
+            let mut bitmap_size = glyph_bounds.size;
+            if params.subpixel_variant.x > 0 {
+                bitmap_size.width += DevicePixels(1);
+            }
+            if params.subpixel_variant.y > 0 {
+                bitmap_size.height += DevicePixels(1);
+            }
 
-            let mut bytes = vec![0; bitmap_size.x() as usize * bitmap_size.y() as usize];
+            let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
             let cx = CGContext::create_bitmap_context(
                 Some(bytes.as_mut_ptr() as *mut _),
-                bitmap_size.x() as usize,
-                bitmap_size.y() as usize,
+                bitmap_size.width.0 as usize,
+                bitmap_size.height.0 as usize,
                 8,
-                bitmap_size.x() as usize,
+                bitmap_size.width.0 as usize,
                 &CGColorSpace::create_device_gray(),
                 kCGImageAlphaOnly,
             );
@@ -268,26 +292,27 @@ impl MacTextSystemState {
             // Move the origin to bottom left and account for scaling, this
             // makes drawing text consistent with the font-kit's raster_bounds.
             cx.translate(
-                -glyph_bounds.origin_x() as CGFloat,
-                (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
+                -glyph_bounds.origin.x.0 as CGFloat,
+                (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
             );
             cx.scale(
-                glyph_id.scale_factor as CGFloat,
-                glyph_id.scale_factor as CGFloat,
+                params.scale_factor as CGFloat,
+                params.scale_factor as CGFloat,
             );
 
-            let subpixel_shift = glyph_id
+            let subpixel_shift = params
                 .subpixel_variant
-                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / glyph_id.scale_factor);
+                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / params.scale_factor);
 
             cx.set_allows_font_subpixel_positioning(true);
             cx.set_should_subpixel_position_fonts(true);
             cx.set_allows_font_subpixel_quantization(false);
             cx.set_should_subpixel_quantize_fonts(false);
-            font.native_font()
-                .clone_with_font_size(f32::from(glyph_id.font_size) as CGFloat)
+            self.fonts[params.font_id.0]
+                .native_font()
+                .clone_with_font_size(f32::from(params.font_size) as CGFloat)
                 .draw_glyphs(
-                    &[u32::from(glyph_id.glyph_id) as CGGlyph],
+                    &[u32::from(params.glyph_id) as CGGlyph],
                     &[CGPoint::new(
                         subpixel_shift.x as CGFloat,
                         subpixel_shift.y as CGFloat,

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

@@ -1,10 +1,10 @@
 use super::{ns_string, MetalRenderer, NSRange};
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
-    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
-    Pixels, Platform, PlatformAtlas, PlatformDispatcher, PlatformInputHandler, PlatformScreen,
-    PlatformWindow, Point, RasterizeGlyphParams, Scene, Size, Timer, WindowAppearance,
-    WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
+    point, px, size, AnyWindowHandle, Bounds, Event, GlyphRasterizationParams, KeyDownEvent,
+    Keystroke, MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
+    MouseMovedEvent, MouseUpEvent, NSRectExt, Pixels, Platform, PlatformAtlas, PlatformDispatcher,
+    PlatformInputHandler, PlatformScreen, PlatformWindow, Point, Scene, Size, Timer,
+    WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -886,7 +886,7 @@ impl PlatformWindow for MacWindow {
         }
     }
 
-    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizeGlyphParams>> {
+    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<GlyphRasterizationParams>> {
         self.0.lock().renderer.glyph_atlas().clone()
     }
 }

crates/gpui3/src/scene.rs 🔗

@@ -1,6 +1,6 @@
 use std::{iter::Peekable, mem};
 
-use super::{Bounds, Hsla, Pixels, Point};
+use super::{Bounds, Hsla, Point};
 use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledPixels};
 use collections::BTreeMap;
 use smallvec::SmallVec;
@@ -35,7 +35,7 @@ impl Scene {
 
         let primitive = primitive.into();
         match primitive {
-            Primitive::Quad(mut quad) => {
+            Primitive::Quad(quad) => {
                 layer.quads.push(quad);
             }
             Primitive::Sprite(sprite) => {
@@ -233,9 +233,9 @@ impl From<Quad> for Primitive {
 #[repr(C)]
 pub struct MonochromeSprite {
     pub order: u32,
-    pub bounds: Bounds<Pixels>,
-    pub clip_bounds: Bounds<Pixels>,
-    pub clip_corner_radii: Corners<Pixels>,
+    pub bounds: Bounds<ScaledPixels>,
+    pub clip_bounds: Bounds<ScaledPixels>,
+    pub clip_corner_radii: Corners<ScaledPixels>,
     pub color: Hsla,
     pub tile: AtlasTile,
 }

crates/gpui3/src/text_system.rs 🔗

@@ -215,9 +215,13 @@ impl TextSystem {
         })
     }
 
+    pub fn raster_bounds(&self, params: &GlyphRasterizationParams) -> Result<Bounds<DevicePixels>> {
+        self.platform_text_system.glyph_raster_bounds(params)
+    }
+
     pub fn rasterize_glyph(
         &self,
-        glyph_id: &RasterizeGlyphParams,
+        glyph_id: &GlyphRasterizationParams,
     ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
         self.platform_text_system.rasterize_glyph(glyph_id)
     }
@@ -380,7 +384,7 @@ pub struct ShapedGlyph {
 }
 
 #[derive(Clone, Debug, PartialEq)]
-pub struct RasterizeGlyphParams {
+pub struct GlyphRasterizationParams {
     pub(crate) font_id: FontId,
     pub(crate) glyph_id: GlyphId,
     pub(crate) font_size: Pixels,
@@ -388,9 +392,9 @@ pub struct RasterizeGlyphParams {
     pub(crate) scale_factor: f32,
 }
 
-impl Eq for RasterizeGlyphParams {}
+impl Eq for GlyphRasterizationParams {}
 
-impl Hash for RasterizeGlyphParams {
+impl Hash for GlyphRasterizationParams {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.font_id.0.hash(state);
         self.glyph_id.0.hash(state);

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

@@ -1,11 +1,10 @@
 use crate::{
-    black, point, px, Bounds, Corners, FontId, Hsla, Layout, MonochromeSprite, Pixels, Point,
-    RunStyle, ShapedBoundary, ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
+    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
+    ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
 use std::sync::Arc;
-use util::ResultExt;
 
 #[derive(Default, Debug, Clone)]
 pub struct Line {
@@ -162,36 +161,14 @@ impl Line {
                     if glyph.is_emoji {
                         todo!()
                     } else {
-                        if let Some(tile) = cx
-                            .rasterize_glyph(
-                                run.font_id,
-                                glyph.id,
-                                self.layout.font_size,
-                                glyph_origin,
-                                cx.scale_factor(),
-                            )
-                            .log_err()
-                        {
-                            let layer_id = cx.current_layer_id();
-
-                            let bounds = Bounds {
-                                origin: glyph_origin + todo!(),
-                                size: todo!(),
-                            };
-                            // cx.text_system().raster_bounds()
-
-                            cx.scene().insert(
-                                layer_id,
-                                MonochromeSprite {
-                                    order: layout.order,
-                                    bounds,
-                                    clip_bounds: bounds,
-                                    clip_corner_radii: Corners::default(),
-                                    color,
-                                    tile,
-                                },
-                            );
-                        }
+                        cx.paint_glyph(
+                            glyph_origin,
+                            layout.order,
+                            run.font_id,
+                            glyph.id,
+                            self.layout.font_size,
+                            color,
+                        )?;
                     }
                 }
 

crates/gpui3/src/window.rs 🔗

@@ -1,8 +1,9 @@
 use crate::{
     px, AnyView, AppContext, AtlasTile, AvailableSpace, Bounds, Context, Effect, Element, EntityId,
-    FontId, GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, Pixels, PlatformAtlas,
-    PlatformWindow, Point, RasterizeGlyphParams, Reference, Scene, Size, StackContext,
-    StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+    FontId, GlyphId, GlyphRasterizationParams, Handle, Hsla, IsZero, LayoutId, MainThread,
+    MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, Reference,
+    Scene, Size, StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+    SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use futures::Future;
@@ -15,7 +16,7 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
-    glyph_atlas: Arc<dyn PlatformAtlas<RasterizeGlyphParams>>,
+    glyph_atlas: Arc<dyn PlatformAtlas<GlyphRasterizationParams>>,
     rem_size: Pixels,
     content_size: Size<Pixels>,
     layout_engine: TaffyLayoutEngine,
@@ -165,6 +166,57 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         })
     }
 
+    pub fn paint_glyph(
+        &mut self,
+        origin: Point<Pixels>,
+        order: u32,
+        font_id: FontId,
+        glyph_id: GlyphId,
+        font_size: Pixels,
+        color: Hsla,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let origin = origin.scale(scale_factor);
+        let subpixel_variant = Point {
+            x: (origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+            y: (origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+        };
+        let params = GlyphRasterizationParams {
+            font_id,
+            glyph_id,
+            font_size,
+            subpixel_variant,
+            scale_factor,
+        };
+
+        let raster_bounds = self.text_system().raster_bounds(&params)?;
+
+        if !raster_bounds.is_zero() {
+            let layer_id = self.current_layer_id();
+            let bounds = Bounds {
+                origin: origin + raster_bounds.origin.map(Into::into),
+                size: raster_bounds.size.map(Into::into),
+            };
+            let tile = self
+                .window
+                .glyph_atlas
+                .get_or_insert_with(&params, &mut || self.text_system().rasterize_glyph(&params))?;
+
+            self.window.scene.insert(
+                layer_id,
+                MonochromeSprite {
+                    order,
+                    bounds,
+                    clip_bounds: bounds,
+                    clip_corner_radii: Default::default(),
+                    color,
+                    tile,
+                },
+            );
+        }
+        Ok(())
+    }
+
     pub fn rasterize_glyph(
         &self,
         font_id: FontId,
@@ -178,7 +230,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             x: (target_position.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
             y: (target_position.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
         };
-        let rasterized_glyph_id = RasterizeGlyphParams {
+        let rasterized_glyph_id = GlyphRasterizationParams {
             font_id,
             glyph_id,
             font_size,