Revert "initial removal attempt"

Junkui Zhang created

This reverts commit 6928488aad1617a5a7c1c2de2391c1a92468e8f8.

Change summary

crates/gpui/src/platform/windows/direct_write.rs | 336 +++++++++++------
1 file changed, 207 insertions(+), 129 deletions(-)

Detailed changes

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<IWICImagingFactory>,
+    d2d1_factory: ID2D1Factory,
     in_memory_loader: IDWriteInMemoryFontFileLoader,
     builder: IDWriteFontSetBuilder1,
     text_renderer: Arc<TextRendererWrapper>,
@@ -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<Self> {
+    pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result<Self> {
         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::<ID2D1DeviceContext4>()?;
+                target.SetTextRenderingParams(&params);
+                target
+            };
 
-            Ok(Self { params })
+            Ok(Self { params, dc_target })
         }
     }
 }
@@ -629,12 +649,17 @@ impl DirectWriteState {
     }
 
     fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+        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)? };
-
-            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],
-                ));
-            }
+            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 mut alpha_data = vec![0u8; (width * height) as usize];
+        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,
+            };
 
-            unsafe {
-                glyph_analysis.CreateAlphaTexture(
-                    texture_type,
-                    &texture_bounds,
-                    &mut alpha_data,
+            // This `cast()` action here should never fail since we are running on Win10+, and
+            // ID2D1DeviceContext4 requires Win8+
+            let render_target = render_target.cast::<ID2D1DeviceContext4>().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(
+                    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,
+                );
             }
-
-            // 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);
+            render_target.EndDraw(None, None)?;
+
+            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<String> {
 }
 
 #[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 {