windows: Fix ascent/descent calculations (#40103)

Cole Miller and Bennet Bo Fenner created

This applies the same fix as #39886 for Windows.

Previously we were using `GetLineMetrics` to determine the ascent and
descent values for each line. It seems like this has the same behavior
as `GetTypographicBounds` on macOS, which is to return the minimum
ascent and descent for the current state of the `TextLayout` object.
This causes the ascent/descent to be unstable when adding or removing an
emoji because a font fallback is triggered when an emoji is present on
the line.

The issue is fixed by switching to `font.GetMetrics` to get the ascent
and descent, which should always return stable values for the main font,
instead of changing when there's a fallback. This also should support
situations where we have multiple explicit fonts on the same line,
although that probably can't be triggered in Zed right now.

Release Notes:

- windows: Fixed a vertical shift in text layout when inserting or
removing an emoji.

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Change summary

crates/gpui/src/platform/windows/direct_write.rs | 22 +++++++++--------
1 file changed, 12 insertions(+), 10 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/direct_write.rs 🔗

@@ -606,19 +606,21 @@ impl DirectWriteState {
             };
 
             let mut first_run = true;
-            let mut ascent = Pixels::default();
-            let mut descent = Pixels::default();
+            let mut max_ascent = 0.0_f32;
+            let mut max_descent = 0.0_f32;
             for run in font_runs {
+                let font_info = &self.fonts[run.font_id.0];
+                let mut metrics = std::mem::zeroed();
+                font_info.font_face.GetMetrics(&mut metrics);
+                let font_scale = font_size.0 / metrics.Base.designUnitsPerEm as f32;
+                max_ascent = max_ascent.max(metrics.Base.ascent as f32 * font_scale);
+                max_descent = max_descent.max(-(metrics.Base.descent as f32 * font_scale));
+
                 if first_run {
                     first_run = false;
-                    let mut metrics = vec![DWRITE_LINE_METRICS::default(); 4];
-                    let mut line_count = 0u32;
-                    text_layout.GetLineMetrics(Some(&mut metrics), &mut line_count as _)?;
-                    ascent = px(metrics[0].baseline);
-                    descent = px(metrics[0].height - metrics[0].baseline);
                     continue;
                 }
-                let font_info = &self.fonts[run.font_id.0];
+
                 let current_text = &text[utf8_offset..(utf8_offset + run.len)];
                 utf8_offset += run.len;
                 let current_text_utf16_length = current_text.encode_utf16().count() as u32;
@@ -660,8 +662,8 @@ impl DirectWriteState {
             Ok(LineLayout {
                 font_size,
                 width,
-                ascent,
-                descent,
+                ascent: max_ascent.into(),
+                descent: max_descent.into(),
                 runs,
                 len: text.len(),
             })