gpui: Fix ascent/descent calculation on macOS (#39886)

Bennet Bo Fenner created

As you can see in the image, we were previously returning different
`ascent`s/`descent`s when a line would/would not contain an Emoji.

<img width="104" height="36" alt="image"
src="https://github.com/user-attachments/assets/436aeda0-87c0-4dee-943b-6da83681d466"
/>

---
CoreTexts `CTLineGetTypographicBounds` seems to return a different
ascent/descent depending on if an Emoji is there or not AFAIK it is not
documented if this is intended behaviour or not. For us it is
undesirable, as typing an Emoji causes the line to be shifted to the
bottom, see here:


https://github.com/user-attachments/assets/2ad1c82e-6297-48ac-a522-fb382ea56eea

--- 
Instead of using `CTLineGetTypographicBounds` to resolve the
ascent/descent, we look at every run and choose the maximum
ascent/descent. This matches how it [works on
Linux](https://github.com/zed-industries/zed/blob/f1d17fcfbef41690fdeb523f0fbddc7c406c5ad6/crates/gpui/src/platform/linux/text_system.rs#L452)

Release Notes:

- Fixed an issue on macOS where typing an emoji on a line would cause
the line to shift downwards by a few pixels

Change summary

crates/gpui/src/platform/mac/text_system.rs | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

Detailed changes

crates/gpui/src/platform/mac/text_system.rs 🔗

@@ -430,6 +430,8 @@ impl MacTextSystemState {
     fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
         // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
         let mut string = CFMutableAttributedString::new();
+        let mut max_ascent = 0.0f32;
+        let mut max_descent = 0.0f32;
         {
             string.replace_str(&CFString::new(text), CFRange::init(0, 0));
             let utf16_line_len = string.char_len() as usize;
@@ -451,6 +453,11 @@ impl MacTextSystemState {
 
                 let font: &FontKitFont = &self.fonts[run.font_id.0];
 
+                let font_metrics = font.metrics();
+                let font_scale = font_size.0 / font_metrics.units_per_em as f32;
+                max_ascent = max_ascent.max(font_metrics.ascent * font_scale);
+                max_descent = max_descent.max(-font_metrics.descent * font_scale);
+
                 unsafe {
                     string.set_attribute(
                         cf_range,
@@ -508,8 +515,8 @@ impl MacTextSystemState {
             runs,
             font_size,
             width: typographic_bounds.width.into(),
-            ascent: typographic_bounds.ascent.into(),
-            descent: typographic_bounds.descent.into(),
+            ascent: max_ascent.into(),
+            descent: max_descent.into(),
             len: text.len(),
         }
     }