From 72a246267f3deb6dd176f26c7f92c3df0e6dafec Mon Sep 17 00:00:00 2001 From: Andros Fenollosa Date: Wed, 3 Jun 2026 17:20:46 +0200 Subject: [PATCH] Metal: draw composition glyph strings (combining accents, ligatures) COMPOSITE_GLYPH had no branch in mtl_draw_glyph_string, so combining sequences (e + COMBINING ACUTE, etc.), ligatures and shaped scripts drew nothing (blank cells). Port ns_draw_composite_glyph_string_foreground to the Metal atlas: char2b[] holds glyph IDs indexed by the composition / gstring index and s->font is the composition's font; static compositions draw each glyph at its table offset, automatic ones walk the LGSTRING applying XOFF/YOFF/WADJUST adjustments, with sequential runs advanced by the natural font advance. Verified: "e+acute a+grave n+tilde o+diaeresis" now renders identically to NS (previously empty). --- src/mtlterm.m | 167 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 32 deletions(-) diff --git a/src/mtlterm.m b/src/mtlterm.m index ee4bf3d9130..6dbd5a924fb 100644 --- a/src/mtlterm.m +++ b/src/mtlterm.m @@ -2060,6 +2060,99 @@ mtl_underline_metrics (struct glyph_string *s, int *position, int *thickness) *thickness = th; } +/* Draw glyph IDs s->char2b[FROM..TO) sequentially from (X, Y baseline), + advancing each glyph by its natural font advance (composition runs are laid + out by font metrics, not by the Emacs column grid). */ +static void +mtl_draw_cmp_run (struct glyph_string *s, MtlFrameData *fd, CTFontRef ctfont, + unsigned long fg, int from, int to, int x, int y) +{ + float pen = (float) x; + for (int k = from; k < to; k++) + { + MtlGlyphCacheEntry *ge = mtl_cache_glyph_id (ctfont, (CGGlyph) s->char2b[k]); + if (!ge) continue; + if (ge->width > 0) + [fd drawGlyph:ge at:CGPointMake (pen, (float) y) color:fg]; + pen += ge->advance_x; + } +} + +/* Draw a composition glyph string: combining accents, ligatures and shaped + scripts (Arabic, Indic). Mirrors ns_draw_composite_glyph_string_foreground + but renders through the Metal glyph atlas instead of font->driver->draw. + char2b[] holds glyph IDs indexed by the composition/gstring index (see + fill_composite_glyph_string / fill_gstring_glyph_string) and s->font is the + composition's font (it can differ from the face font). */ +static void +mtl_draw_composite_glyph_string (struct glyph_string *s, MtlFrameData *fd, + unsigned long fg) +{ + int x; + if (s->face && s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p) + x = s->x + max (s->face->box_vertical_line_width, 0); + else + x = s->x; + + CTFontRef ctfont = s->font ? (CTFontRef) macfont_get_nsctfont (s->font) : NULL; + if (!ctfont || s->font_not_found_p) + { + /* Placeholder outline when the composition's font is missing. */ + if (s->cmp_from == 0) + { + unsigned long cc = ns_color_to_pixel (FRAME_CURSOR_COLOR (s->f)); + [fd fillRect:NSMakeRect (s->x, s->y, s->width - 1, 1) color:cc]; + [fd fillRect:NSMakeRect (s->x, s->y + s->height - 2, s->width - 1, 1) color:cc]; + [fd fillRect:NSMakeRect (s->x, s->y, 1, s->height - 1) color:cc]; + [fd fillRect:NSMakeRect (s->x + s->width - 2, s->y, 1, s->height - 1) color:cc]; + } + return; + } + + if (!s->first_glyph->u.cmp.automatic) + { + /* Static composition: each glyph at an explicit offset from the table. */ + int y = s->ybase; + int i, j; + for (i = 0, j = s->cmp_from; i < s->nchars; i++, j++) + if (COMPOSITION_GLYPH (s->cmp, j) != '\t') + { + int xx = x + s->cmp->offsets[j * 2]; + int yy = y - s->cmp->offsets[j * 2 + 1]; + mtl_draw_cmp_run (s, fd, ctfont, fg, j, j + 1, xx, yy); + } + } + else + { + /* Automatic composition (shaping): LGLYPHs, some with adjustments. */ + Lisp_Object gstring = composition_gstring_from_id (s->cmp_id); + int y = s->ybase; + int width = 0, i, j; + + for (i = j = s->cmp_from; i < s->cmp_to; i++) + { + Lisp_Object glyph = LGSTRING_GLYPH (gstring, i); + if (NILP (LGLYPH_ADJUSTMENT (glyph))) + width += LGLYPH_WIDTH (glyph); + else + { + if (j < i) + { + mtl_draw_cmp_run (s, fd, ctfont, fg, j, i, x, y); + x += width; + } + mtl_draw_cmp_run (s, fd, ctfont, fg, i, i + 1, + x + LGLYPH_XOFF (glyph), y + LGLYPH_YOFF (glyph)); + x += LGLYPH_WADJUST (glyph); + width = 0; + j = i + 1; + } + } + if (j < i) + mtl_draw_cmp_run (s, fd, ctfont, fg, j, i, x, y); + } +} + static void mtl_draw_glyph_string_impl (struct glyph_string *s) { @@ -2124,42 +2217,52 @@ mtl_draw_glyph_string_impl (struct glyph_string *s) /* Skip stretch glyphs (background already filled) */ if (s->first_glyph->type == STRETCH_GLYPH) return; - /* Get CoreText font */ - CTFontRef ctfont = mtl_ctfont_for_face (face); - if (!ctfont) { mtl_dgs_nofont_count++; return; } - - - /* Advance using Emacs's own integer glyph grid (first_glyph[i].pixel_width), - NOT the CoreText float advance. Re-advancing by the font's fractional - advance drifts away from the layout Emacs computed: glyphs land at - fractional positions (linear sampling blurs them) and progressively - overlap/clip across the line. Keeping integer pen positions also makes the - 1:1 blit pixel-crisp. */ - int pen_x = s->x; - int baseline_y = s->ybase; - - for (int i = 0; i < s->nchars; i++) + if (s->first_glyph->type == COMPOSITE_GLYPH) { - /* char2b contains GLYPH IDs for the macfont backend — NOT Unicode codepoints. - Use mtl_cache_glyph_id which calls CoreText with the ID directly. */ - CGGlyph glyphId = s->char2b ? (CGGlyph)s->char2b[i] : 0; - int adv = (i < s->nchars) ? s->first_glyph[i].pixel_width - : FRAME_COLUMN_WIDTH (f); + /* Combining accents, ligatures, shaped scripts. */ + mtl_draw_composite_glyph_string (s, fd, fg); + } + else + { + /* Get CoreText font */ + CTFontRef ctfont = mtl_ctfont_for_face (face); + if (!ctfont) { mtl_dgs_nofont_count++; return; } - if (glyphId) + /* Advance using Emacs's own integer glyph grid + (first_glyph[i].pixel_width), NOT the CoreText float advance. + Re-advancing by the font's fractional advance drifts away from the + layout Emacs computed: glyphs land at fractional positions (linear + sampling blurs them) and progressively overlap/clip across the line. + Keeping integer pen positions also makes the 1:1 blit pixel-crisp. */ + int pen_x = s->x; + int baseline_y = s->ybase; + + for (int i = 0; i < s->nchars; i++) { - MtlGlyphCacheEntry *ge = mtl_cache_glyph_id (ctfont, glyphId); - if (ge && ge->width > 0) - { - mtl_dgs_drawn_count++; - /* bearing_y: distance from glyph top-left to baseline. - In our top-left coord system, glyph top = baseline_y - bearing_y. */ - [fd drawGlyph:ge at:CGPointMake ((float)pen_x, (float)baseline_y) - color:fg]; - } - } + /* char2b contains GLYPH IDs for the macfont backend — NOT Unicode + codepoints. Use mtl_cache_glyph_id which calls CoreText with the + ID directly. */ + CGGlyph glyphId = s->char2b ? (CGGlyph)s->char2b[i] : 0; + int adv = (i < s->nchars) ? s->first_glyph[i].pixel_width + : FRAME_COLUMN_WIDTH (f); - pen_x += adv; + if (glyphId) + { + MtlGlyphCacheEntry *ge = mtl_cache_glyph_id (ctfont, glyphId); + if (ge && ge->width > 0) + { + mtl_dgs_drawn_count++; + /* bearing_y: distance from glyph top-left to baseline. In + our top-left coord system, glyph top = baseline_y - + bearing_y. */ + [fd drawGlyph:ge at:CGPointMake ((float)pen_x, + (float)baseline_y) + color:fg]; + } + } + + pen_x += adv; + } } /* Underline. Wave FIRST: FACE_UNDERLINE_WAVE is above FACE_UNDERLINE_SINGLE