From df385b87fa817d4f02449373fa3219d645318a76 Mon Sep 17 00:00:00 2001 From: Andros Fenollosa Date: Tue, 2 Jun 2026 16:37:17 +0200 Subject: [PATCH] Metal: compute the underline position from font metrics (F2) The underline was drawn at s->ybase + s->underline_position, but underline_position/underline_thickness are filled in by each backend (the NS backend computes them in ns_draw_text_decoration) and were left at 0 here, so the line landed on the baseline and cut through the bottom of the glyphs. Add mtl_underline_metrics, mirroring the NS computation: position from font->underline_position (or descent/2, or the descent line per the face), thickness from the font (min 1), clamped to stay inside the cell, and matching a previous underlined run for continuity. Handle single, double (second line above), and colored underlines; wave falls back to a straight line at the same position for now. Verified vs NS: the underline is now a full-width bar just below the glyph body instead of through it; AE unchanged at ~0.88%. --- src/mtlterm.m | 84 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/mtlterm.m b/src/mtlterm.m index 8967db10e72..201f6f80ca1 100644 --- a/src/mtlterm.m +++ b/src/mtlterm.m @@ -1913,6 +1913,57 @@ mtl_draw_glyph_string_box (struct glyph_string *s, MtlFrameData *fd) [fd fillRect:NSMakeRect (x + w - vth, y, vth, h) color:br]; /* right */ } +/* Compute the underline offset below the baseline and its thickness, mirroring + ns_draw_text_decoration. These depend on font metrics; the glyph string's + underline_position/underline_thickness are otherwise left uninitialized, so + the underline was being drawn at the baseline (offset 0), cutting through the + bottom of the glyphs. Honors the face's descent-line options and uses the + default underline-minimum-offset (1) and x-use-underline-position-properties + (t). */ +static void +mtl_underline_metrics (struct glyph_string *s, int *position, int *thickness) +{ + /* Match a previous underlined run so a continued underline stays seamless. */ + if (s->prev + && s->prev->face->underline != FACE_UNDERLINE_WAVE + && s->prev->face->underline >= FACE_UNDERLINE_SINGLE + && s->prev->underline_thickness > 0 + && (s->prev->face->underline_at_descent_line_p + == s->face->underline_at_descent_line_p) + && (s->prev->face->underline_pixels_above_descent_line + == s->face->underline_pixels_above_descent_line)) + { + *thickness = s->prev->underline_thickness; + *position = s->prev->underline_position; + return; + } + + struct font *font = font_for_underline_metrics (s); + int descent = s->y + s->height - s->ybase; + int minimum_offset = 1; + int th = (font && font->underline_thickness > 0) ? font->underline_thickness : 1; + int pos; + + if (s->face->underline_at_descent_line_p) + pos = descent - th - s->face->underline_pixels_above_descent_line; + else if (font && font->underline_position >= 0) + pos = font->underline_position; + else if (font) + pos = lround (font->descent / 2.0); + else + pos = minimum_offset; + + if (!s->face->underline_pixels_above_descent_line) + pos = max (pos, minimum_offset); + + /* Keep the underline inside the cell. */ + if (descent <= pos) { pos = descent - 1; th = 1; } + else if (descent < pos + th) th = 1; + + *position = pos; + *thickness = th; +} + static void mtl_draw_glyph_string_impl (struct glyph_string *s) { @@ -2010,14 +2061,33 @@ mtl_draw_glyph_string_impl (struct glyph_string *s) pen_x += adv; } - /* Underline */ - if (face && face->underline != FACE_NO_UNDERLINE) + /* Underline. Single/double lines (the common case for buttons and links) + use font-derived position/thickness; wave falls back to a straight line at + the same position for now. */ + if (face && face->underline >= FACE_UNDERLINE_SINGLE) { - int uth = s->underline_thickness > 0 ? s->underline_thickness : 1; - int uybase = s->ybase + s->underline_position; - unsigned long uc = face->underline_defaulted_p - ? fg : face->underline_color; - [fd fillRect:NSMakeRect (s->x, uybase, s->width, uth) color:uc]; + int position, thickness; + mtl_underline_metrics (s, &position, &thickness); + s->underline_thickness = thickness; + s->underline_position = position; + unsigned long uc = face->underline_defaulted_p ? fg : face->underline_color; + [fd fillRect:NSMakeRect (s->x, s->ybase + position, s->width, thickness) + color:uc]; + /* Second line above the first for double underline. */ + if (face->underline == FACE_UNDERLINE_DOUBLE_LINE) + { + int p2 = position - thickness - 1; + [fd fillRect:NSMakeRect (s->x, s->ybase + p2, s->width, thickness) + color:uc]; + } + } + else if (face && face->underline == FACE_UNDERLINE_WAVE) + { + int position, thickness; + mtl_underline_metrics (s, &position, &thickness); + unsigned long uc = face->underline_defaulted_p ? fg : face->underline_color; + [fd fillRect:NSMakeRect (s->x, s->ybase + position, s->width, thickness) + color:uc]; } /* Strike-through */