diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index 3fc143fdcb..ada306c15c 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc}; +use std::{borrow::Cow, sync::Arc}; use ::util::ResultExt; use anyhow::Result; @@ -9,7 +9,13 @@ use windows::{ Win32::{ Foundation::*, Globalization::GetUserDefaultLocaleName, - Graphics::{DirectWrite::*, Dxgi::Common::*, Gdi::LOGFONTW, Imaging::*}, + Graphics::{ + Direct2D::{Common::*, *}, + DirectWrite::*, + Dxgi::Common::*, + Gdi::LOGFONTW, + Imaging::*, + }, System::SystemServices::LOCALE_NAME_MAX_LENGTH, UI::WindowsAndMessaging::*, }, @@ -34,6 +40,7 @@ struct DirectWriteComponent { locale: String, factory: IDWriteFactory5, bitmap_factory: AgileReference, + d2d1_factory: ID2D1Factory, in_memory_loader: IDWriteInMemoryFontFileLoader, builder: IDWriteFontSetBuilder1, text_renderer: Arc, @@ -42,6 +49,7 @@ struct DirectWriteComponent { struct GlyphRenderContext { params: IDWriteRenderingParams3, + dc_target: ID2D1DeviceContext4, } struct DirectWriteState { @@ -66,6 +74,8 @@ impl DirectWriteComponent { unsafe { let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?; let bitmap_factory = AgileReference::new(bitmap_factory)?; + let d2d1_factory: ID2D1Factory = + D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?; // The `IDWriteInMemoryFontFileLoader` here is supported starting from // Windows 10 Creators Update, which consequently requires the entire // `DirectWriteTextSystem` to run on `win10 1703`+. @@ -76,12 +86,13 @@ 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)?; + let render_context = GlyphRenderContext::new(&factory, &d2d1_factory)?; Ok(DirectWriteComponent { locale, factory, bitmap_factory, + d2d1_factory, in_memory_loader, builder, text_renderer, @@ -92,7 +103,7 @@ impl DirectWriteComponent { } impl GlyphRenderContext { - pub fn new(factory: &IDWriteFactory5) -> Result { + pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result { unsafe { let default_params: IDWriteRenderingParams3 = factory.CreateRenderingParams()?.cast()?; @@ -111,8 +122,17 @@ impl GlyphRenderContext { 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 }) + Ok(Self { params, dc_target }) } } } @@ -629,12 +649,17 @@ impl DirectWriteState { } 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]; let offset = [DWRITE_GLYPH_OFFSET::default()]; let glyph_run = DWRITE_GLYPH_RUN { - fontFace: ManuallyDrop::new(Some(font.font_face.cast()?)), + fontFace: unsafe { std::mem::transmute_copy(&font.font_face) }, fontEmSize: params.font_size.0, glyphCount: 1, glyphIndices: glyph_id.as_ptr(), @@ -643,29 +668,13 @@ impl DirectWriteState { isSideways: BOOL(0), bidiLevel: 0, }; - - let transform = DWRITE_MATRIX::default(); - let rendering_mode = DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC; - let measuring_mode = DWRITE_MEASURING_MODE_NATURAL; - let baseline_origin_x = 0.0; - let baseline_origin_y = 0.0; - - let glyph_analysis = unsafe { - self.components.factory.CreateGlyphRunAnalysis( + let bounds = unsafe { + render_target.GetGlyphRunWorldBounds( + Vector2 { X: 0.0, Y: 0.0 }, &glyph_run, - Some(&transform as *const _), - rendering_mode, - measuring_mode, - DWRITE_GRID_FIT_MODE_DEFAULT, - DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE, - baseline_origin_x, - baseline_origin_y, + DWRITE_MEASURING_MODE_NATURAL, )? }; - - let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1; - let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? }; - // todo(windows) // This is a walkaround, deleted when figured out. let y_offset; @@ -687,13 +696,12 @@ impl DirectWriteState { } else { Ok(Bounds { origin: point( - ((bounds.left as f32 * params.scale_factor).ceil() as i32).into(), - ((bounds.top as f32 * params.scale_factor).ceil() as i32 + y_offset).into(), + ((bounds.left * params.scale_factor).ceil() as i32).into(), + ((bounds.top * params.scale_factor).ceil() as i32 + y_offset).into(), ), size: size( - (((bounds.right - bounds.left) as f32 * params.scale_factor).ceil() as i32) - .into(), - (((bounds.bottom - bounds.top) as f32 * params.scale_factor).ceil() as i32 + (((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(), + (((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32 + extra_height) .into(), ), @@ -731,7 +739,7 @@ impl DirectWriteState { ascenderOffset: glyph_bounds.origin.y.0 as f32 / params.scale_factor, }]; let glyph_run = DWRITE_GLYPH_RUN { - fontFace: ManuallyDrop::new(Some(font_info.font_face.cast()?)), + fontFace: unsafe { std::mem::transmute_copy(&font_info.font_face) }, fontEmSize: params.font_size.0, glyphCount: 1, glyphIndices: glyph_id.as_ptr(), @@ -751,108 +759,149 @@ impl DirectWriteState { } let bitmap_size = bitmap_size; - let subpixel_shift = params - .subpixel_variant - .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); - let baseline_origin_x = subpixel_shift.x / params.scale_factor; - let baseline_origin_y = subpixel_shift.y / params.scale_factor; - - 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 rendering_mode = if params.is_emoji { - DWRITE_RENDERING_MODE1_NATURAL - } else { - DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC - }; - - let measuring_mode = DWRITE_MEASURING_MODE_NATURAL; - - let glyph_analysis = unsafe { - self.components.factory.CreateGlyphRunAnalysis( - &glyph_run, - Some(&transform), - rendering_mode, - measuring_mode, - DWRITE_GRID_FIT_MODE_DEFAULT, - DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE, - baseline_origin_x, - baseline_origin_y, - )? - }; - + 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 { - // For emoji, we need to handle color glyphs differently - // This is a simplified approach - in a full implementation you'd want to - // properly handle color glyph runs using TranslateColorGlyphRun - let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1; - let texture_bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? }; - - let width = (texture_bounds.right - texture_bounds.left) as u32; - let height = (texture_bounds.bottom - texture_bounds.top) as u32; - - if width == 0 || height == 0 { - return Ok(( - bitmap_size, - vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize * 4], - )); - } - - let mut rgba_data = vec![0u8; (width * height * 4) as usize]; - - unsafe { - glyph_analysis.CreateAlphaTexture(texture_type, &texture_bounds, &mut rgba_data)?; - } - - // Resize to match expected bitmap_size if needed - let expected_size = bitmap_size.width.0 as usize * bitmap_size.height.0 as usize * 4; - rgba_data.resize(expected_size, 0); - - Ok((bitmap_size, rgba_data)) + total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize * 4; + bitmap_format = &GUID_WICPixelFormat32bppPBGRA; + 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 { - // For regular text, use grayscale or cleartype - let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1; - let texture_bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? }; + total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize; + bitmap_format = &GUID_WICPixelFormat8bppAlpha; + 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; + } - let width = (texture_bounds.right - texture_bounds.left) as u32; - let height = (texture_bounds.bottom - texture_bounds.top) as u32; + let bitmap_factory = self.components.bitmap_factory.resolve()?; + unsafe { + let bitmap = bitmap_factory.CreateBitmap( + bitmap_width, + bitmap_height, + bitmap_format, + WICBitmapCacheOnLoad, + )?; + let render_target = self + .components + .d2d1_factory + .CreateWicBitmapRenderTarget(&bitmap, &render_target_property)?; + let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, None)?; + let subpixel_shift = params + .subpixel_variant + .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); + let baseline_origin = Vector2 { + X: subpixel_shift.x / params.scale_factor, + Y: subpixel_shift.y / params.scale_factor, + }; - if width == 0 || height == 0 { - return Ok(( - bitmap_size, - vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize], - )); - } + // 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(); - let mut alpha_data = vec![0u8; (width * height) as usize]; - - unsafe { - glyph_analysis.CreateAlphaTexture( - texture_type, - &texture_bounds, - &mut alpha_data, + if params.is_emoji { + // WARN: only DWRITE_GLYPH_IMAGE_FORMATS_COLR has been tested + let enumerator = self.components.factory.TranslateColorGlyphRun( + baseline_origin, + &glyph_run as _, + None, + DWRITE_GLYPH_IMAGE_FORMATS_COLR + | DWRITE_GLYPH_IMAGE_FORMATS_SVG + | DWRITE_GLYPH_IMAGE_FORMATS_PNG + | DWRITE_GLYPH_IMAGE_FORMATS_JPEG + | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8, + DWRITE_MEASURING_MODE_NATURAL, + None, + 0, )?; + while enumerator.MoveNext().is_ok() { + let Ok(color_glyph) = enumerator.GetCurrentRun() else { + break; + }; + let color_glyph = &*color_glyph; + let brush_color = translate_color(&color_glyph.Base.runColor); + brush.SetColor(&brush_color); + match color_glyph.glyphImageFormat { + DWRITE_GLYPH_IMAGE_FORMATS_PNG + | DWRITE_GLYPH_IMAGE_FORMATS_JPEG + | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 => render_target + .DrawColorBitmapGlyphRun( + color_glyph.glyphImageFormat, + baseline_origin, + &color_glyph.Base.glyphRun, + color_glyph.measuringMode, + D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION_DEFAULT, + ), + DWRITE_GLYPH_IMAGE_FORMATS_SVG => render_target.DrawSvgGlyphRun( + baseline_origin, + &color_glyph.Base.glyphRun, + &brush, + None, + color_glyph.Base.paletteIndex as u32, + color_glyph.measuringMode, + ), + _ => render_target.DrawGlyphRun( + baseline_origin, + &color_glyph.Base.glyphRun, + Some(color_glyph.Base.glyphRunDescription as *const _), + &brush, + color_glyph.measuringMode, + ), + } + } + } else { + render_target.DrawGlyphRun( + baseline_origin, + &glyph_run, + None, + &brush, + DWRITE_MEASURING_MODE_NATURAL, + ); } + render_target.EndDraw(None, None)?; - // For cleartype, we need to convert the 3x1 subpixel data to grayscale - // This is a simplified conversion - you might want to do proper subpixel rendering - let mut grayscale_data = Vec::new(); - for chunk in alpha_data.chunks_exact(3) { - let avg = (chunk[0] as u32 + chunk[1] as u32 + chunk[2] as u32) / 3; - grayscale_data.push(avg as u8); + let mut raw_data = vec![0u8; total_bytes]; + 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.; + 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; + } + } else { + let scaler = 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)?; } - - // Resize to match expected bitmap_size if needed - let expected_size = bitmap_size.width.0 as usize * bitmap_size.height.0 as usize; - grayscale_data.resize(expected_size, 0); - - Ok((bitmap_size, grayscale_data)) + Ok((bitmap_size, raw_data)) } } @@ -1422,8 +1471,13 @@ fn get_name(string: IDWriteLocalizedStrings, locale: &str) -> Result { } #[inline] -fn translate_color(color: &DWRITE_COLOR_F) -> [f32; 4] { - [color.r, color.g, color.b, color.a] +fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F { + D2D1_COLOR_F { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } } fn get_system_ui_font_name() -> SharedString { @@ -1450,6 +1504,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, + } +} + // One would think that with newer DirectWrite method: IDWriteFontFace4::GetGlyphImageFormats // but that doesn't seem to work for some glyphs, say ❤ fn is_color_glyph( @@ -1489,6 +1561,12 @@ fn is_color_glyph( } const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US"); +const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, +}; #[cfg(test)] mod tests {