diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index 99d9fd0a6f6c13cd8a2ae11542aaefdc6c75b4ec..615a0e717f48f33b96baadb8c34449a6dd3c4031 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -8,7 +8,6 @@ use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use windows::{ core::*, - Foundation::Numerics::Matrix3x2, Win32::{ Foundation::*, Globalization::GetUserDefaultLocaleName, @@ -45,6 +44,12 @@ struct DirectWriteComponent { in_memory_loader: IDWriteInMemoryFontFileLoader, builder: IDWriteFontSetBuilder1, text_renderer: Arc, + render_context: GlyphRenderContext, +} + +struct GlyphRenderContext { + params: IDWriteRenderingParams3, + dc_target: ID2D1DeviceContext4, } // All use of the IUnknown methods should be "thread-safe". @@ -86,6 +91,7 @@ impl DirectWriteComponent { GetUserDefaultLocaleName(&mut locale_vec); let locale = String::from_utf16_lossy(&locale_vec); let text_renderer = Arc::new(TextRendererWrapper::new(&locale)); + let render_context = GlyphRenderContext::new(&factory, &d2d1_factory)?; Ok(DirectWriteComponent { locale, @@ -95,11 +101,47 @@ impl DirectWriteComponent { in_memory_loader, builder, text_renderer, + render_context, }) } } } +impl GlyphRenderContext { + pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result { + unsafe { + let default_params: IDWriteRenderingParams3 = + factory.CreateRenderingParams()?.cast()?; + let gamma = default_params.GetGamma(); + let enhanced_contrast = default_params.GetEnhancedContrast(); + let gray_contrast = default_params.GetGrayscaleEnhancedContrast(); + let cleartype_level = default_params.GetClearTypeLevel(); + let grid_fit_mode = default_params.GetGridFitMode(); + + let params = factory.CreateCustomRenderingParams( + gamma, + enhanced_contrast, + gray_contrast, + cleartype_level, + DWRITE_PIXEL_GEOMETRY_RGB, + DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC, + grid_fit_mode, + )?; + let dc_target = { + let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property( + DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED, + ))?; + let target = target.cast::()?; + target.SetTextRenderingParams(¶ms); + target + }; + + Ok(Self { params, dc_target }) + } + } +} + impl DirectWriteTextSystem { pub(crate) fn new() -> Result { let components = DirectWriteComponent::new()?; @@ -521,10 +563,12 @@ impl DirectWriteState { } } - unsafe fn get_glyphrun_analysis( - &self, - params: &RenderGlyphParams, - ) -> windows::core::Result { + fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { + let render_target = &self.components.render_context.dc_target; + unsafe { + render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS); + render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor); + } let font = &self.fonts[params.font_id.0]; let glyph_id = [params.glyph_id.0 as u16]; let advance = [0.0f32]; @@ -539,40 +583,29 @@ impl DirectWriteState { isSideways: BOOL(0), bidiLevel: 0, }; - let transform = DWRITE_MATRIX { - m11: params.scale_factor, - m12: 0.0, - m21: 0.0, - m22: params.scale_factor, - dx: 0.0, - dy: 0.0, + let bounds = unsafe { + render_target.GetGlyphRunWorldBounds( + D2D_POINT_2F { x: 0.0, y: 0.0 }, + &glyph_run, + DWRITE_MEASURING_MODE_NATURAL, + )? }; - self.components.factory.CreateGlyphRunAnalysis( - &glyph_run as _, - Some(&transform as _), - DWRITE_RENDERING_MODE1_NATURAL, - DWRITE_MEASURING_MODE_NATURAL, - DWRITE_GRID_FIT_MODE_DEFAULT, - DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE, - 0.0, - 0.0, - ) - } - - fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { - unsafe { - let glyph_run_analysis = self.get_glyphrun_analysis(params)?; - let bounds = glyph_run_analysis.GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1)?; + if bounds.right < bounds.left { Ok(Bounds { - origin: Point { - x: DevicePixels(bounds.left), - y: DevicePixels(bounds.top), - }, - size: Size { - width: DevicePixels(bounds.right - bounds.left), - height: DevicePixels(bounds.bottom - bounds.top), - }, + origin: point(0.into(), 0.into()), + size: size(0.into(), 0.into()), + }) + } else { + Ok(Bounds { + origin: point( + ((bounds.left * params.scale_factor).ceil() as i32).into(), + ((bounds.top * params.scale_factor).ceil() as i32).into(), + ), + size: size( + (((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(), + (((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32).into(), + ), }) } } @@ -626,66 +659,40 @@ impl DirectWriteState { bitmap_size.height += DevicePixels(1); } let bitmap_size = bitmap_size; - let transform = DWRITE_MATRIX { - m11: params.scale_factor, - m12: 0.0, - m21: 0.0, - m22: params.scale_factor, - dx: 0.0, - dy: 0.0, - }; - let brush_property = D2D1_BRUSH_PROPERTIES { - opacity: 1.0, - transform: Matrix3x2 { - M11: params.scale_factor, - M12: 0.0, - M21: 0.0, - M22: params.scale_factor, - M31: 0.0, - M32: 0.0, - }, - }; let total_bytes; let bitmap_format; let render_target_property; + let bitmap_width; + let bitmap_height; let bitmap_stride; + let bitmap_dpi; if params.is_emoji { total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize * 4; bitmap_format = &GUID_WICPixelFormat32bppPBGRA; - render_target_property = D2D1_RENDER_TARGET_PROPERTIES { - r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT, - pixelFormat: D2D1_PIXEL_FORMAT { - format: DXGI_FORMAT_B8G8R8A8_UNORM, - alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED, - }, - dpiX: params.scale_factor * 96.0, - dpiY: params.scale_factor * 96.0, - usage: D2D1_RENDER_TARGET_USAGE_NONE, - minLevel: D2D1_FEATURE_LEVEL_DEFAULT, - }; + render_target_property = get_render_target_property( + DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED, + ); + bitmap_width = bitmap_size.width.0 as u32; + bitmap_height = bitmap_size.height.0 as u32; bitmap_stride = bitmap_size.width.0 as u32 * 4; + bitmap_dpi = 96.0; } else { total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize; bitmap_format = &GUID_WICPixelFormat8bppAlpha; - render_target_property = D2D1_RENDER_TARGET_PROPERTIES { - r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT, - pixelFormat: D2D1_PIXEL_FORMAT { - format: DXGI_FORMAT_A8_UNORM, - alphaMode: D2D1_ALPHA_MODE_STRAIGHT, - }, - dpiX: params.scale_factor * 96.0, - dpiY: params.scale_factor * 96.0, - usage: D2D1_RENDER_TARGET_USAGE_NONE, - minLevel: D2D1_FEATURE_LEVEL_DEFAULT, - }; + render_target_property = + get_render_target_property(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT); + bitmap_width = bitmap_size.width.0 as u32 * 2; + bitmap_height = bitmap_size.height.0 as u32 * 2; bitmap_stride = bitmap_size.width.0 as u32; + bitmap_dpi = 192.0; } unsafe { let bitmap = self.components.bitmap_factory.CreateBitmap( - bitmap_size.width.0 as u32, - bitmap_size.height.0 as u32, + bitmap_width, + bitmap_height, bitmap_format, WICBitmapCacheOnLoad, )?; @@ -693,7 +700,7 @@ impl DirectWriteState { .components .d2d1_factory .CreateWicBitmapRenderTarget(&bitmap, &render_target_property)?; - let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, Some(&brush_property))?; + let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, None)?; let subpixel_shift = params .subpixel_variant .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); @@ -705,7 +712,14 @@ impl DirectWriteState { // This `cast()` action here should never fail since we are running on Win10+, and // ID2D1DeviceContext4 requires Win8+ let render_target = render_target.cast::().unwrap(); + render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS); + render_target.SetDpi( + bitmap_dpi * params.scale_factor, + bitmap_dpi * params.scale_factor, + ); + render_target.SetTextRenderingParams(&self.components.render_context.params); render_target.BeginDraw(); + if params.is_emoji { // WARN: only DWRITE_GLYPH_IMAGE_FORMATS_COLR has been tested let enumerator = self.components.factory.TranslateColorGlyphRun( @@ -718,7 +732,7 @@ impl DirectWriteState { | DWRITE_GLYPH_IMAGE_FORMATS_JPEG | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8, DWRITE_MEASURING_MODE_NATURAL, - Some(&transform as _), + None, 0, )?; while enumerator.MoveNext().is_ok() { @@ -766,9 +780,10 @@ impl DirectWriteState { ); } render_target.EndDraw(None, None)?; + let mut raw_data = vec![0u8; total_bytes]; - bitmap.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?; if params.is_emoji { + bitmap.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?; // Convert from BGRA with premultiplied alpha to BGRA with straight alpha. for pixel in raw_data.chunks_exact_mut(4) { let a = pixel[3] as f32 / 255.; @@ -776,6 +791,15 @@ impl DirectWriteState { pixel[1] = (pixel[1] as f32 / a) as u8; pixel[2] = (pixel[2] as f32 / a) as u8; } + } else { + let scaler = self.components.bitmap_factory.CreateBitmapScaler()?; + scaler.Initialize( + &bitmap, + bitmap_size.width.0 as u32, + bitmap_size.height.0 as u32, + WICBitmapInterpolationModeHighQualityCubic, + )?; + scaler.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?; } Ok((bitmap_size, raw_data)) } @@ -1243,8 +1267,8 @@ fn make_direct_write_feature(feature_name: &str, parameter: u32) -> DWRITE_FONT_ #[inline] fn make_open_type_tag(tag_name: &str) -> u32 { - assert_eq!(tag_name.chars().count(), 4); let bytes = tag_name.bytes().collect_vec(); + assert_eq!(bytes.len(), 4); ((bytes[3] as u32) << 24) | ((bytes[2] as u32) << 16) | ((bytes[1] as u32) << 8) @@ -1327,6 +1351,24 @@ fn get_system_ui_font_name() -> SharedString { } } +#[inline] +fn get_render_target_property( + pixel_format: DXGI_FORMAT, + alpha_mode: D2D1_ALPHA_MODE, +) -> D2D1_RENDER_TARGET_PROPERTIES { + D2D1_RENDER_TARGET_PROPERTIES { + r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT, + pixelFormat: D2D1_PIXEL_FORMAT { + format: pixel_format, + alphaMode: alpha_mode, + }, + dpiX: 96.0, + dpiY: 96.0, + usage: D2D1_RENDER_TARGET_USAGE_NONE, + minLevel: D2D1_FEATURE_LEVEL_DEFAULT, + } +} + const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US"); const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F { r: 1.0,