diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 1179a83618ab8420170490fe817d3f03da06333c..e7e7e4a4fb69c1893b68eb21615d8121343aafc2 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -6,8 +6,8 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, MonochromeSprite, - Pixels, Point, RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size, + AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point, + RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -165,13 +165,8 @@ pub trait PlatformTextSystem: Send + Sync { fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option; fn rasterize_glyph( &self, - font_id: FontId, - font_size: f32, - glyph_id: GlyphId, - subpixel_shift: Point, - scale_factor: f32, - options: RasterizationOptions, - ) -> Option<(Bounds, Vec)>; + glyph_id: &RasterizedGlyphId, + ) -> Result<(Bounds, Vec)>; fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine; fn wrap_line( &self, @@ -185,9 +180,9 @@ pub trait PlatformTextSystem: Send + Sync { pub trait PlatformAtlas: Send + Sync { fn get_or_insert_with( &self, - key: Key, - build: &dyn Fn() -> (Size, Vec), - ) -> AtlasTile; + key: &Key, + build: &mut dyn FnMut() -> Result<(Size, Vec)>, + ) -> Result; fn clear(&self); } diff --git a/crates/gpui3/src/platform/mac/metal_atlas.rs b/crates/gpui3/src/platform/mac/metal_atlas.rs index 96cb1a64ed5eb0b6d721d008e930598cd7ab89b2..4e3d661670bea7aeceefe1f844ef76c97e03a192 100644 --- a/crates/gpui3/src/platform/mac/metal_atlas.rs +++ b/crates/gpui3/src/platform/mac/metal_atlas.rs @@ -1,4 +1,5 @@ use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size}; +use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; use etagere::BucketedAtlasAllocator; @@ -38,28 +39,30 @@ struct MetalAtlasState { impl PlatformAtlas for MetalAtlas where - Key: Eq + Hash + Send, + Key: Clone + Eq + Hash + Send, { fn get_or_insert_with( &self, - key: Key, - build: &dyn Fn() -> (Size, Vec), - ) -> AtlasTile { + key: &Key, + build: &mut dyn FnMut() -> Result<(Size, Vec)>, + ) -> Result { let mut lock = self.0.lock(); - if let Some(tile) = lock.tiles_by_key.get(&key) { - return tile.clone(); + if let Some(tile) = lock.tiles_by_key.get(key) { + return Ok(tile.clone()); } else { - let (size, bytes) = build(); - lock.textures + let (size, bytes) = build()?; + let tile = lock + .textures .iter_mut() .rev() .find_map(|texture| texture.allocate(size, &bytes)) - .unwrap_or_else(|| { + .or_else(|| { let texture = lock.push_texture(size); - texture - .allocate(size, &bytes) - .expect("could not allocate a tile in new texture") + texture.allocate(size, &bytes) }) + .ok_or_else(|| anyhow!("could not allocate in new texture"))?; + lock.tiles_by_key.insert(key.clone(), tile.clone()); + Ok(tile) } } diff --git a/crates/gpui3/src/platform/mac/text_system.rs b/crates/gpui3/src/platform/mac/text_system.rs index 9f1ea8a78f82303e0c52201c6e0195763641994b..f28badbb629216f64a1109c7c28bc92bcd1a5e3a 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/crates/gpui3/src/platform/mac/text_system.rs @@ -1,8 +1,9 @@ use crate::{ - point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, - GlyphId, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, ShapedGlyph, - ShapedLine, ShapedRun, SharedString, Size, + point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, + FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RasterizedGlyphId, Result, ShapedGlyph, + ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, }; +use anyhow::anyhow; use cocoa::appkit::{CGFloat, CGPoint}; use collections::HashMap; use core_foundation::{ @@ -11,11 +12,7 @@ use core_foundation::{ base::{CFRange, TCFType}, string::CFString, }; -use core_graphics::{ - base::{kCGImageAlphaPremultipliedLast, CGGlyph}, - color_space::CGColorSpace, - context::CGContext, -}; +use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext}; use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; use font_kit::{ font::Font as FontKitFont, @@ -139,21 +136,9 @@ impl PlatformTextSystem for MacTextSystem { fn rasterize_glyph( &self, - font_id: FontId, - font_size: f32, - glyph_id: GlyphId, - subpixel_shift: Point, - scale_factor: f32, - options: RasterizationOptions, - ) -> Option<(Bounds, Vec)> { - self.0.read().rasterize_glyph( - font_id, - font_size, - glyph_id, - subpixel_shift, - scale_factor, - options, - ) + glyph_id: &RasterizedGlyphId, + ) -> Result<(Bounds, Vec)> { + self.0.read().rasterize_glyph(glyph_id) } fn layout_line( @@ -247,63 +232,41 @@ impl MacTextSystemState { fn rasterize_glyph( &self, - font_id: FontId, - font_size: f32, - glyph_id: GlyphId, - subpixel_shift: Point, - scale_factor: f32, - options: RasterizationOptions, - ) -> Option<(Bounds, Vec)> { - let font = &self.fonts[font_id.0]; - let scale = Transform2F::from_scale(scale_factor); - let glyph_bounds = font - .raster_bounds( - glyph_id.into(), - font_size, - scale, - HintingOptions::None, - font_kit::canvas::RasterizationOptions::GrayscaleAa, - ) - .ok()?; + glyph_id: &RasterizedGlyphId, + ) -> Result<(Bounds, Vec)> { + 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 { - None + Err(anyhow!("glyph bounds are empty")) } else { // Make room for subpixel variants. - let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32); + let subpixel_padding = Vector2I::new( + glyph_id.subpixel_variant.x.min(1) as i32, + glyph_id.subpixel_variant.y.min(1) as i32, + ); let cx_bounds = RectI::new( glyph_bounds.origin(), - glyph_bounds.size() + Vector2I::from(subpixel_padding), + glyph_bounds.size() + subpixel_padding, ); - let mut bytes; - let cx; - match options { - RasterizationOptions::Alpha => { - bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize]; - cx = CGContext::create_bitmap_context( - Some(bytes.as_mut_ptr() as *mut _), - cx_bounds.width() as usize, - cx_bounds.height() as usize, - 8, - cx_bounds.width() as usize, - &CGColorSpace::create_device_gray(), - kCGImageAlphaOnly, - ); - } - RasterizationOptions::Bgra => { - bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize]; - cx = CGContext::create_bitmap_context( - Some(bytes.as_mut_ptr() as *mut _), - cx_bounds.width() as usize, - cx_bounds.height() as usize, - 8, - cx_bounds.width() as usize * 4, - &CGColorSpace::create_device_rgb(), - kCGImageAlphaPremultipliedLast, - ); - } - } + let mut bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize]; + let cx = CGContext::create_bitmap_context( + Some(bytes.as_mut_ptr() as *mut _), + cx_bounds.width() as usize, + cx_bounds.height() as usize, + 8, + cx_bounds.width() as usize, + &CGColorSpace::create_device_gray(), + kCGImageAlphaOnly, + ); // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. @@ -311,35 +274,31 @@ impl MacTextSystemState { -glyph_bounds.origin_x() as CGFloat, (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat, ); - cx.scale(scale_factor as CGFloat, scale_factor as CGFloat); + cx.scale( + glyph_id.scale_factor as CGFloat, + glyph_id.scale_factor as CGFloat, + ); + + let subpixel_shift = glyph_id + .subpixel_variant + .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / glyph_id.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(font_size as CGFloat) + .clone_with_font_size(f32::from(glyph_id.font_size) as CGFloat) .draw_glyphs( - &[u32::from(glyph_id) as CGGlyph], + &[u32::from(glyph_id.glyph_id) as CGGlyph], &[CGPoint::new( - (f32::from(subpixel_shift.x) / scale_factor) as CGFloat, - (f32::from(subpixel_shift.y) / scale_factor) as CGFloat, + subpixel_shift.x as CGFloat, + subpixel_shift.y as CGFloat, )], cx, ); - if let RasterizationOptions::Bgra = options { - // Convert from RGBA with premultiplied alpha to BGRA with straight alpha. - for pixel in bytes.chunks_exact_mut(4) { - pixel.swap(0, 2); - let a = pixel[3] as f32 / 255.; - pixel[0] = (pixel[0] as f32 / a) as u8; - pixel[1] = (pixel[1] as f32 / a) as u8; - pixel[2] = (pixel[2] as f32 / a) as u8; - } - } - - Some((cx_bounds.into(), bytes)) + Ok((cx_bounds.into(), bytes)) } } @@ -549,11 +508,17 @@ impl From for Bounds { } } -impl From for Bounds { +impl From for Bounds { fn from(rect: RectI) -> Self { Bounds { - origin: point(rect.origin_x() as u32, rect.origin_y() as u32), - size: size(rect.width() as u32, rect.height() as u32), + origin: point( + DevicePixels(rect.origin_x() as u32), + DevicePixels(rect.origin_y() as u32), + ), + size: size( + DevicePixels(rect.width() as u32), + DevicePixels(rect.height() as u32), + ), } } } diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index 4d8a0e59fbc3d2d2e682ccd69065d8476bd47a34..84a6f54470537ee470ea0366d34cf681debf222f 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -11,7 +11,8 @@ use line_wrapper::*; pub use text_layout_cache::*; use crate::{ - px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle, + px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, RasterizationOptions, + Result, SharedString, Size, UnderlineStyle, }; use collections::HashMap; use core::fmt; @@ -30,6 +31,8 @@ pub struct FontId(pub usize); #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] pub struct FontFamilyId(pub usize); +pub const SUBPIXEL_VARIANTS: u8 = 4; + pub struct TextSystem { text_layout_cache: Arc, platform_text_system: Arc, @@ -212,6 +215,13 @@ impl TextSystem { text_system: self.clone(), }) } + + pub fn rasterize_glyph( + &self, + glyph_id: &RasterizedGlyphId, + ) -> Result<(Bounds, Vec)> { + self.platform_text_system.rasterize_glyph(glyph_id) + } } #[derive(Hash, Eq, PartialEq)] @@ -370,11 +380,25 @@ pub struct ShapedGlyph { pub is_emoji: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, PartialEq)] pub struct RasterizedGlyphId { - font_id: FontId, - glyph_id: GlyphId, - font_size: Pixels, + pub(crate) font_id: FontId, + pub(crate) glyph_id: GlyphId, + pub(crate) font_size: Pixels, + pub(crate) subpixel_variant: Point, + pub(crate) scale_factor: f32, +} + +impl Eq for RasterizedGlyphId {} + +impl Hash for RasterizedGlyphId { + fn hash(&self, state: &mut H) { + self.font_id.0.hash(state); + self.glyph_id.0.hash(state); + self.font_size.0.to_bits().hash(state); + self.subpixel_variant.hash(state); + self.scale_factor.to_bits().hash(state); + } } #[derive(Clone, Debug, Eq, PartialEq, Hash)] diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index c7444192070c9ac286840082166fcc241fa2eca5..71ec13461f6d72966aea4b57f23c2b9e0b4d00c5 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -1,6 +1,6 @@ use crate::{ - black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary, - ShapedLine, ShapedRun, UnderlineStyle, WindowContext, + black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RasterizedGlyphId, RunStyle, + ShapedBoundary, ShapedLine, ShapedRun, UnderlineStyle, WindowContext, }; use anyhow::Result; use smallvec::SmallVec; diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 9fe5e3bc19bb80dd2623240e8f1d0fb72c934eb5..97351e7869b617180e1ecb37b96a11e714826259 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,8 +1,9 @@ use crate::{ - px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, FontId, - GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, Pixels, PlatformAtlas, PlatformWindow, - Point, RasterizedGlyphId, Reference, Scene, Size, StackContext, StackingOrder, Style, - TaffyLayoutEngine, WeakHandle, WindowOptions, + px, AnyView, AppContext, AtlasTile, AvailableSpace, Bounds, Context, DevicePixels, Effect, + Element, EntityId, FontId, GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, + MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, RasterizedGlyphId, Reference, + Scene, Size, StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::Result; use futures::Future; @@ -161,6 +162,39 @@ impl<'a, 'w> WindowContext<'a, 'w> { }) } + pub fn rasterize_glyph( + &self, + font_id: FontId, + glyph_id: GlyphId, + font_size: Pixels, + scale_factor: f32, + target_position: Point, + ) -> Result<(AtlasTile, Point)> { + let target_position = target_position * scale_factor; + let subpixel_variant = Point { + 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 = RasterizedGlyphId { + font_id, + glyph_id, + font_size, + subpixel_variant, + scale_factor, + }; + let mut offset = Default::default(); + let tile = self + .window + .glyph_atlas + .get_or_insert_with(&rasterized_glyph_id, &mut || { + let (bounds, pixels) = self.text_system().rasterize_glyph(&rasterized_glyph_id)?; + offset = bounds.origin; + Ok((bounds.size, pixels)) + })?; + + Ok((tile, offset)) + } + pub(crate) fn draw(&mut self) -> Result<()> { let unit_entity = self.unit_entity.clone(); self.update_entity(&unit_entity, |_, cx| {