Re: [PATCH] emoji on TUI emacs
Kai Ma <[email protected]> Sun, 09 Aug 2026 20:42:12 +0200
| Newsgroups | gmane.emacs.devel |
|---|---|
| Message-ID | <[email protected]> |
Thanks. Please find the updated patch series below. Eli Zaretskii <[email protected]> writes: >> However, the column number change due to C-f, C-b, etc. is still the >> total sum of CHARACTER_WIDTH (not necessarily 2). Do we want to change >> it as well? > > Yes, definitely. current-column and move-to-column should both work > correctly in these cases. Fixed. Previously in composition_update_it, the width is computed by summing character widths. I'm not entirely sure whether it's correct to sum glyph widths here. >> --- a/lisp/composite.el >> +++ b/lisp/composite.el >> @@ -899,11 +899,18 @@ auto-compose-chars >> >> This function is the default value of `auto-composition-function' (which see)." >> (let ((gstring (composition-get-gstring from to font-object string))) >> - (if (lgstring-shaped-p gstring) >> - gstring >> - (or (fontp font-object 'font-object) >> - (setq func 'compose-gstring-for-terminal)) >> - (funcall func gstring direction)))) >> + (cond >> + ((lgstring-shaped-p gstring) >> + gstring) >> + ((and (eq func #'compose-gstring-and-emoji) >> + (fontp font-object 'font-object)) >> + (compose-gstring-for-graphic gstring direction)) >> + ((eq func #'compose-gstring-and-emoji) >> + (compose-gstring-and-emoji gstring direction)) >> + ((fontp font-object 'font-object) >> + (funcall func gstring direction)) >> + (t >> + (compose-gstring-for-terminal gstring direction))))) > > I think the above logic could benefit from comments explaining why we > dispatch the different cases as we do. Done. >> + DEFVAR_LISP ("auto-composition-emoji-tty-eligible-codepoints", Vauto_composition_emoji_tty_eligible_codepoints, >> + doc: /* List of codepoints for which auto-composition will check for an emoji font on tty display. > > This line is too long. Our conventions are not to exceed 79 > characters. I suggest to shorten it as follows: > > List of characters displayed as Emoji on TTY frames when followed by > VS-16. Fixed. >> +These are codepoints which have Emoji_Presentation = No, and thus by >> +default are not displayed as emoji. When followed by U+FE0F (VS-16), > ^^^^^ >> +they may be considered as the beginning of an emoji sequence instead. > ^^^^^ > "Emoji", capitalized. > >> +This list is auto-generated, you should not need to modify it. */); >> + Vauto_composition_emoji_tty_eligible_codepoints = Qnil; >> + >> + DEFVAR_BOOL ("tty-display-emoji-force-wide", tty_display_emoji_force_wide, >> + doc: /* Whether Emoji sequences on TTY frames should always be considered wide. >> + >> +When nil, emoji sequences whose first character's width is 1 may be > ^^^^^ > "Emoji" Fixed. > >> +considered as a narrow glyph to be compatible with some non-compliant >> +terminal emulators. */); >> + tty_display_emoji_force_wide = true; > > I think these two variables are related (basically, the first one > controls which sequences will be considered to be wide, right?). So I > think they should each mention the other one. Fixed. > >> @@ -379,9 +377,8 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, >> chars = end - i; >> bytes = string_char_to_byte (string, end) - i_byte; >> } >> -#ifdef HAVE_WINDOW_SYSTEM >> else if (auto_comp >> - && f && FRAME_WINDOW_P (f) >> + && f >> && multibyte >> && find_automatic_composition (i, -1, i, &ignore, >> &end, &val, string) >> @@ -396,7 +393,7 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, >> >> /* The below is somewhat expensive, so compute it only once >> for the entire loop, and only if needed. */ >> - if (font_width < 0) >> + if (FRAME_WINDOW_P (f) && font_width < 0) >> { >> font_width = FRAME_COLUMN_WIDTH (f); >> default_font = Fface_font (Qdefault, Qnil, Qnil); >> @@ -417,11 +414,13 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, >> } >> } >> } >> - thiswidth = (double) pixelwidth / font_width + 0.5; >> + >> + thiswidth = FRAME_WINDOW_P (f) >> + ? ((double) pixelwidth / font_width + 0.5) >> + : pixelwidth; >> chars = end - i; >> bytes = string_char_to_byte (string, end) - i_byte; >> } >> -#endif /* HAVE_WINDOW_SYSTEM */ >> else >> { >> int c; > > This is not quite right: in an Emacs build --without-x some of the > functions mentioned in the code fragment which was previously under > HAVE_WINDOW_SYSTEM do not exist, so this will fail to link. What you > need is to move the HAVE_WINDOW_SYSTEM condition inside this fragment, > such that it is still in effect when functions like Ffont_info are > called. I think the entire block which starts with > > if (font_width < 0) > > should be under the HAVE_WINDOW_SYSTEM condition, since it makes no > sense on a TTY frame. Fixed. > > Finally, I think these changes and the new variables warrant a NEWS > entry. Done.
0001-Change-widths-of-East-Asian-Neutral-characters-to-1.patch
(text/x-diff, 1.8 KB)
From 10d1fa2b424d67d1111abc12114e285f95ae0b43 Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Wed, 5 Aug 2026 00:56:18 +0200 Subject: [PATCH 1/6] Change widths of East Asian Neutral characters to 1. In accordance to UAX #11 ED7 (https://www.unicode.org/reports/tr11/tr11-44.html#ED7) > [...] because for all practical purposes they behave like Na, they are > treated as narrow characters (the same as Na) under the > recommendations below. * lisp/international/characters.el: Do not mark U+2690--U+2692, U+1F1AD, U+1FA00--U+1FA53, U+1FA60--U+1FA6D, and U+1FB00--U+1FB92 as double-width, since they have East_Asian_Width=Neutral in Unicode 17. --- lisp/international/characters.el | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lisp/international/characters.el b/lisp/international/characters.el index d3024b95cd3..4f66aca6ebf 100644 --- a/lisp/international/characters.el +++ b/lisp/international/characters.el @@ -1304,7 +1304,7 @@ ?L (#x2648 . #x2653) (#x267F . #x267F) (#x268A . #x268F) - (#x2690 . #x2693) + (#x2693 . #x2693) (#x26A1 . #x26A1) (#x26AA . #x26AB) (#x26BD . #x26BE) @@ -1374,7 +1374,6 @@ ?L (#x1F0CF . #x1F0CF) (#x1F18E . #x1F18E) (#x1F191 . #x1F19A) - (#x1F1AD . #x1F1AD) (#x1F200 . #x1F202) (#x1F210 . #x1F23B) (#x1F240 . #x1F248) @@ -1413,8 +1412,6 @@ ?L (#x1F90C . #x1F93A) (#x1F93C . #x1F945) (#x1F947 . #x1F9FF) - (#x1FA00 . #x1FA53) - (#x1FA60 . #x1FA6D) (#x1FA70 . #x1FA7C) (#x1FA80 . #x1FA8A) (#x1FA8E . #x1FAC6) @@ -1422,7 +1419,6 @@ ?L (#x1FACD . #x1FADC) (#x1FADF . #x1FAEA) (#x1FAEF . #x1FAF8) - (#x1FB00 . #x1FB92) (#x20000 . #x2FFFF) (#x30000 . #x3FFFF)))) (dolist (elt l) -- 2.53.0
0002-Compose-emoji-sequences-on-tty-frames.patch
(text/x-diff, 6.4 KB)
From d7930b3641a227acef357c9cde37bddb31b808cc Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Sat, 8 Aug 2026 18:46:09 +0200 Subject: [PATCH 2/6] Compose emoji sequences on tty frames. * admin/unidata/Makefile.in: Changed. * admin/unidata/emoji-zwj.awk: Scan emoji-variation-sequences.txt to generate auto-composition-emoji-tty-eligible-codepoints. Update the default composition function to compose-gstring-and-emoji. * composite.el (compose-gstring-and-emoji): Add. (auto-compose-chars): Changed to consider compose-gstring-and-emoji. --- admin/unidata/Makefile.in | 2 +- admin/unidata/emoji-zwj.awk | 23 +++++++++++++++++++---- lisp/composite.el | 33 ++++++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 10 deletions(-) mode change 100644 => 100755 admin/unidata/emoji-zwj.awk diff --git a/admin/unidata/Makefile.in b/admin/unidata/Makefile.in index b954990cf9e..6407abd1c4b 100644 --- a/admin/unidata/Makefile.in +++ b/admin/unidata/Makefile.in @@ -116,7 +116,7 @@ .PHONY: emoji-zwj.el: ${unidir}/emoji-zwj.el zwj = ${srcdir}/emoji-zwj.awk -zwj_sources = ${srcdir}/emoji-zwj-sequences.txt $(srcdir)/emoji-sequences.txt +zwj_sources = ${srcdir}/emoji-zwj-sequences.txt $(srcdir)/emoji-sequences.txt $(srcdir)/emoji-variation-sequences.txt ${unidir}/emoji-zwj.el: ${zwj} # Don't use $^, since that includes the awk script. diff --git a/admin/unidata/emoji-zwj.awk b/admin/unidata/emoji-zwj.awk old mode 100644 new mode 100755 index 1cb7508e443..1795152a391 --- a/admin/unidata/emoji-zwj.awk +++ b/admin/unidata/emoji-zwj.awk @@ -60,6 +60,11 @@ vec[elts[1]] = vec[elts[1]] "\"" } +/^[0-9A-F]+ FE0F *; *emoji style;/ { + tty_trigger_codepoints[++n_tty_triggers] = $1 + ch[$1] = $1 +} + END { print ";;; emoji-zwj.el --- emoji zwj character composition table -*- lexical-binding:t -*-" print ";;; Automatically generated from admin/unidata/emoji-{zwj-,}sequences.txt" @@ -86,13 +91,23 @@ END { print "(setq auto-composition-emoji-eligible-codepoints" print "'(" - for (trig in trigger_codepoints) { print "?\\N{U+" trigger_codepoints[trig] "}" } print "))" + # On tty terminals, emoji sequences are simply composed and the + # rendering is delegated to the terminal emulators. All codepoints + # in the variation sequence is considered a valid beginning. + print "(setq auto-composition-emoji-tty-eligible-codepoints" + print "'(" + for (trig in tty_trigger_codepoints) + { + print "?\\N{U+" tty_trigger_codepoints[trig] "}" + } + print "))" + # We add entries for 'codepoint U+FE0F' here to ensure that the # code in font_range is triggered. @@ -114,7 +129,7 @@ END { print " (nconc (char-table-range composition-function-table (car elt))" print " (list (vector (cdr elt)" print " 0" - print " #'compose-gstring-for-graphic)))))" + print " #'compose-gstring-and-emoji)))))" print ";; The following two blocks are derived by hand from emoji-sequences.txt" print ";; FIXME: add support for Emoji_Keycap_Sequence once we learn how to respect FE0F/VS-16" @@ -126,7 +141,7 @@ END { print " (nconc (char-table-range composition-function-table '(#x1F1E6 . #x1F1FF))" print " (list (vector \"[\\U0001F1E6-\\U0001F1FF][\\U0001F1E6-\\U0001F1FF]\"" print " 0" - print " #'compose-gstring-for-graphic))))" + print " #'compose-gstring-and-emoji))))" print ";; UK Flags" print "(set-char-table-range composition-function-table" @@ -134,7 +149,7 @@ END { print " (nconc (char-table-range composition-function-table #x1F3F4)" print " (list (vector \"\\U0001F3F4\\U000E0067\\U000E0062\\\\(?:\\U000E0065\\U000E006E\\U000E0067\\\\|\\U000E0073\\U000E0063\\U000E0074\\\\|\\U000E0077\\U000E006C\\U000E0073\\\\)\\U000E007F\"" print " 0" - print " #'compose-gstring-for-graphic))))" + print " #'compose-gstring-and-emoji))))" printf "\n(provide 'emoji-zwj)" } diff --git a/lisp/composite.el b/lisp/composite.el index d6e923f7f28..ce22b4bbbce 100644 --- a/lisp/composite.el +++ b/lisp/composite.el @@ -899,11 +899,22 @@ auto-compose-chars This function is the default value of `auto-composition-function' (which see)." (let ((gstring (composition-get-gstring from to font-object string))) - (if (lgstring-shaped-p gstring) - gstring - (or (fontp font-object 'font-object) - (setq func 'compose-gstring-for-terminal)) - (funcall func gstring direction)))) + (cond + ((lgstring-shaped-p gstring) + gstring) + ;; The graphical composition function already handles the + ;; composition of Emoji sequences based on the font. On textual + ;; terminals, we compose Emoji sequences and leave the actual + ;; rendering to the terminal emulator. + ((and (eq func #'compose-gstring-and-emoji) + (fontp font-object 'font-object)) + (compose-gstring-for-graphic gstring direction)) + ((eq func #'compose-gstring-and-emoji) + (compose-gstring-and-emoji gstring direction)) + ((fontp font-object 'font-object) + (funcall func gstring direction)) + (t + (compose-gstring-for-terminal gstring direction))))) (put 'auto-composition-mode 'permanent-local t) @@ -936,6 +947,18 @@ global-auto-composition-mode (defalias 'toggle-auto-composition 'auto-composition-mode) +(defun compose-gstring-and-emoji (gstring _direction) + "Compose Emoji sequences into a grapheme cluster. +This function is only called on tty terminals. On graphical displays, +`auto-compose-chars' will call `compose-gstring-for-graphic' instead." + ;; Emoji sequences are matched exactly. + (let ((nglyphs (lgstring-char-len gstring))) + (dotimes (i nglyphs) + (let ((glyph (lgstring-glyph gstring i))) + (when glyph + (lglyph-set-from-to glyph 0 (1- nglyphs)))))) + gstring) + (provide 'composite) ;;; composite.el ends here -- 2.53.0
0003-Produce-padding-glyphs-for-composite-glyphs.patch
(text/x-diff, 8.8 KB)
From cd1b6e2144bffac7b655b45e891df899fbde3c19 Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Thu, 30 Jul 2026 16:24:28 +0200 Subject: [PATCH 3/6] Produce padding glyphs for composite glyphs. * src/term.c (append_composite_glyph): Add `padding' argument. (produce_composite_glyph): Request one padding glyph if the composite glyph has pixel_width >= 2 and the first character has width >= 2, or it is considered for an emoji. (encode_terminal_code): Skip padding glyphs for composite glyphs as well. (composite_glyph_is_emoji_sequence): Add. (tty-display-emoji-force-wide): Add. (auto-composition-emoji-tty-eligible-codepoints): Add. --- src/term.c | 169 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 127 insertions(+), 42 deletions(-) diff --git a/src/term.c b/src/term.c index 7e05f5b5139..7eb7d2a2a7d 100644 --- a/src/term.c +++ b/src/term.c @@ -567,7 +567,7 @@ encode_terminal_code (struct glyph *src, int src_len, nchars = 0; while (src < src_end) { - if (src->type == COMPOSITE_GLYPH) + if (src->type == COMPOSITE_GLYPH && ! CHAR_GLYPH_PADDING_P (*src)) { struct composition *cmp; Lisp_Object gstring UNINIT; @@ -1528,7 +1528,7 @@ #define CONDITIONAL_REASSIGN(cap1, cap2, sym) \ available from the initial frame as in batch mode. */ static void append_glyph (struct it *); -static void append_composite_glyph (struct it *); +static void append_composite_glyph (struct it *, int padding); static void produce_composite_glyph (struct it *); static void append_glyphless_glyph (struct it *, int, const char *); static void produce_glyphless_glyph (struct it *, Lisp_Object); @@ -1775,9 +1775,10 @@ produce_glyphs (struct it *it) face. */ static void -append_composite_glyph (struct it *it) +append_composite_glyph (struct it *it, int padding) { - struct glyph *glyph; + struct glyph *glyph, *end; + int i, width = 1 + padding; eassert (it->glyph_row); glyph = it->glyph_row->glyphs[it->area] + it->glyph_row->used[it->area]; @@ -1789,74 +1790,125 @@ append_composite_glyph (struct it *it) && !it->glyph_row->full_width_p && !WINDOW_RIGHTMOST_P (it->w) && WINDOW_RIGHT_MARGIN_WIDTH (it->w) == 0; - if (glyph < it->glyph_row->glyphs[1 + it->area] - reserve_last) + end = it->glyph_row->glyphs[1 + it->area] - reserve_last; + if (glyph < end) { /* If the glyph row is reversed, we need to prepend the glyph rather than append it. */ if (it->glyph_row->reversed_p && it->area == TEXT_AREA) { struct glyph *g; + int move_by = width; /* Make room for the new glyph. */ + if (move_by > end - glyph) + move_by = end - glyph; for (g = glyph - 1; g >= it->glyph_row->glyphs[it->area]; g--) - g[1] = *g; + g[move_by] = *g; glyph = it->glyph_row->glyphs[it->area]; + end = glyph + move_by; } - glyph->type = COMPOSITE_GLYPH; - eassert (it->pixel_width <= SHRT_MAX); - glyph->pixel_width = it->pixel_width; - glyph->u.cmp.id = it->cmp_it.id; - if (it->cmp_it.ch < 0) - { - glyph->u.cmp.automatic = 0; - glyph->u.cmp.id = it->cmp_it.id; - } - else + + eassert(it->pixel_width <= SHRT_MAX); + for (i = 0; + i < width && glyph < end; + ++i) { - glyph->u.cmp.automatic = 1; + glyph->type = COMPOSITE_GLYPH; + glyph->pixel_width = it->pixel_width - padding; glyph->u.cmp.id = it->cmp_it.id; - glyph->slice.cmp.from = it->cmp_it.from; - glyph->slice.cmp.to = it->cmp_it.to - 1; - } + if (it->cmp_it.ch < 0) + { + glyph->u.cmp.automatic = 0; + glyph->u.cmp.id = it->cmp_it.id; + } + else + { + glyph->u.cmp.automatic = 1; + glyph->u.cmp.id = it->cmp_it.id; + glyph->slice.cmp.from = it->cmp_it.from; + glyph->slice.cmp.to = it->cmp_it.to - 1; + } + glyph->avoid_cursor_p = it->avoid_cursor_p; + glyph->multibyte_p = it->multibyte_p; + glyph->frame = it->f; + glyph->face_id = it->face_id; + glyph->padding_p = i > 0; + glyph->charpos = CHARPOS (it->position); + glyph->object = it->object; + if (it->bidi_p) + { + glyph->resolved_level = it->bidi_it.resolved_level; + eassert ((it->bidi_it.type & 7) == it->bidi_it.type); + glyph->bidi_type = it->bidi_it.type; + } + else + { + glyph->resolved_level = 0; + glyph->bidi_type = UNKNOWN_BT; + } - glyph->avoid_cursor_p = it->avoid_cursor_p; - glyph->multibyte_p = it->multibyte_p; - glyph->frame = it->f; - glyph->face_id = it->face_id; - glyph->padding_p = false; - glyph->charpos = CHARPOS (it->position); - glyph->object = it->object; - if (it->bidi_p) - { - glyph->resolved_level = it->bidi_it.resolved_level; - eassert ((it->bidi_it.type & 7) == it->bidi_it.type); - glyph->bidi_type = it->bidi_it.type; - } - else - { - glyph->resolved_level = 0; - glyph->bidi_type = UNKNOWN_BT; + ++it->glyph_row->used[it->area]; + ++glyph; } + } +} - ++it->glyph_row->used[it->area]; - ++glyph; + +/* For some emoji sequences, the first character width might be 1. + Possibly render them as wide glyphs if it's requested. */ + +static bool +composite_glyph_is_emoji_sequence (struct it *it) +{ + Lisp_Object gstring; + int first, second; + + /* Skip static compositions. */ + if (it->cmp_it.ch < 0) + return false; + + gstring = composition_gstring_from_id (it->cmp_it.id); + if (it->cmp_it.from + 1 >= it->cmp_it.to) + return false; + first = XFIXNUM (LGSTRING_CHAR (gstring, it->cmp_it.from)); + second = XFIXNUM (LGSTRING_CHAR (gstring, it->cmp_it.from + 1)); + + /* Base + VS16/Modifier. */ + if (second == 0xFE0F || (0x1F3FB <= second && second <= 0x1F3FF)) + { + return ! NILP (Fmemq (make_fixnum (first), + Vauto_composition_emoji_tty_eligible_codepoints)); } + + /* Regional Indicators ("Flags" in emoji_zwj.awk). */ + if (0x1F1E6 <= first && first <= 0x1F1FF + && 0x1F1E6 <= second && second <= 0x1F1FF) + return true; + + return false; } /* Produce a composite glyph for iterator IT. IT->cmp_id is the ID of the composition. We simply produces components of the composition assuming that the terminal has a capability to layout/render it - correctly. */ + correctly. A padding glyph is added if both the composition and the + first character has width >= 2. As a special case, if the composite + glyph is determined to be an emoji sequence, it may also add a + padding glyph. */ static void produce_composite_glyph (struct it *it) { + int padding, first_char; + if (it->cmp_it.ch < 0) { struct composition *cmp = composition_table[it->cmp_it.id]; it->pixel_width = cmp->width; + first_char = it->c; } else { @@ -1864,10 +1916,16 @@ produce_composite_glyph (struct it *it) it->pixel_width = composition_gstring_width (gstring, it->cmp_it.from, it->cmp_it.to, NULL); + first_char = LGLYPH_CHAR (LGSTRING_GLYPH (gstring, + it->cmp_it.from)); } - it->nglyphs = 1; + + padding = (it->pixel_width >= 2 && CHARACTER_WIDTH (first_char) >= 2) + || (tty_display_emoji_force_wide + && composite_glyph_is_emoji_sequence (it)); + it->nglyphs = 1 + padding; if (it->glyph_row) - append_composite_glyph (it); + append_composite_glyph (it, padding); } @@ -5277,6 +5335,33 @@ syms_of_term (void) using TABs for cursor motion. */); tty_cursor_movement_use_TAB = 1; + DEFVAR_LISP ("auto-composition-emoji-tty-eligible-codepoints", Vauto_composition_emoji_tty_eligible_codepoints, + doc: /* List of characters displayed as Emoji on TTY frames when followed by VS-16. + +These are codepoints which have Emoji_Presentation = No, and thus by +default are not displayed as Emoji. When followed by U+FE0F (VS-16), +they may be considered as the beginning of an Emoji sequence instead. + +Some initial code points may have character width 1, and non-compliant +terminals may render them narrow. In that case, set +`tty-display-emoji-force-wide' to nil. + +This list is auto-generated, you should not need to modify it. */); + Vauto_composition_emoji_tty_eligible_codepoints = Qnil; + + DEFVAR_BOOL ("tty-display-emoji-force-wide", tty_display_emoji_force_wide, + doc: /* Whether Emoji sequences on TTY frames should always be considered wide. + +On tty, an Emoji sequence considered for composition may begin with any +code point in `auto-composition-emoji-tty-eligible-codepoints', which +does not necessarily has width 2. A compliant terminal emulator +typically render an Emoji sequence as a wide glyph. + +When nil, an Emoji sequence whose first character's width is 1 may be +considered as a narrow glyph to be compatible with some non-compliant +terminal emulators. */); + tty_display_emoji_force_wide = true; + defsubr (&Stty_display_color_p); defsubr (&Stty_display_color_cells); defsubr (&Stty_no_underline); -- 2.53.0
0004-Make-string-width-return-2-for-an-emoji.patch
(text/x-diff, 2.9 KB)
From 1b0135a3b0159d0c2d0ce90f0ec61ab1b42ee648 Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Sat, 8 Aug 2026 19:42:31 +0200 Subject: [PATCH 4/6] Make string-width return 2 for an emoji. * lisp/composite.el (compose-gstring-and-emoji): Adjust the widths of glyphs. * src/character.c (lisp_string_width): Handle auto_comp when HAVE_WINDOW_SYSTEM is not defined. --- lisp/composite.el | 4 +++- src/character.c | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lisp/composite.el b/lisp/composite.el index ce22b4bbbce..b2a832fb771 100644 --- a/lisp/composite.el +++ b/lisp/composite.el @@ -956,7 +956,9 @@ compose-gstring-and-emoji (dotimes (i nglyphs) (let ((glyph (lgstring-glyph gstring i))) (when glyph - (lglyph-set-from-to glyph 0 (1- nglyphs)))))) + (lglyph-set-from-to glyph 0 (1- nglyphs)) + ;; Adjust the total width of the gstring to 2. + (lglyph-set-width glyph (if (= i 0) 2 0)))))) gstring) (provide 'composite) diff --git a/src/character.c b/src/character.c index 6347617d508..4d6ad9a0b8e 100644 --- a/src/character.c +++ b/src/character.c @@ -353,12 +353,12 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, ptrdiff_t from_byte = i_byte; ptrdiff_t width = 0; struct Lisp_Char_Table *dp = buffer_display_table (); -#ifdef HAVE_WINDOW_SYSTEM struct frame *f = (FRAMEP (selected_frame) && FRAME_LIVE_P (XFRAME (selected_frame))) ? XFRAME (selected_frame) : NULL; int font_width = -1; +#ifdef HAVE_WINDOW_SYSTEM Lisp_Object default_font, frame_font; #endif @@ -379,9 +379,8 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, chars = end - i; bytes = string_char_to_byte (string, end) - i_byte; } -#ifdef HAVE_WINDOW_SYSTEM else if (auto_comp - && f && FRAME_WINDOW_P (f) + && f && multibyte && find_automatic_composition (i, -1, i, &ignore, &end, &val, string) @@ -394,9 +393,10 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, int pixelwidth = composition_gstring_width (val, 0, j, NULL); +#ifdef HAVE_WINDOW_SYSTEM /* The below is somewhat expensive, so compute it only once for the entire loop, and only if needed. */ - if (font_width < 0) + if (FRAME_WINDOW_P (f) && font_width < 0) { font_width = FRAME_COLUMN_WIDTH (f); default_font = Fface_font (Qdefault, Qnil, Qnil); @@ -417,11 +417,14 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to, } } } - thiswidth = (double) pixelwidth / font_width + 0.5; +#endif + + thiswidth = FRAME_WINDOW_P (f) + ? ((double) pixelwidth / font_width + 0.5) + : pixelwidth; chars = end - i; bytes = string_char_to_byte (string, end) - i_byte; } -#endif /* HAVE_WINDOW_SYSTEM */ else { int c; -- 2.53.0
0005-Compute-composition-width-using-glyph-widths.patch
(text/x-diff, 1.1 KB)
From cafe5e982bc9ab32b3b506b6563b279d92e2244a Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Sun, 9 Aug 2026 19:44:49 +0200 Subject: [PATCH 5/6] Compute composition width using glyph widths. * src/composite.c (composition_update_it): Set cmp_it->width to be the sum of all glyph widths, instead of character widths. --- src/composite.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/composite.c b/src/composite.c index 55841d08cb5..ce596be9d2c 100644 --- a/src/composite.c +++ b/src/composite.c @@ -1536,11 +1536,13 @@ composition_update_it (struct composition_it *cmp_it, ptrdiff_t charpos, ptrdiff cmp_it->nchars = LGLYPH_TO (glyph) + 1 - from; cmp_it->nbytes = 0; cmp_it->width = 0; - for (i = cmp_it->nchars - 1; i >= 0; i--) + + for (i = cmp_it->nglyphs - 1; i >= 0; i--) { - c = XFIXNUM (LGSTRING_CHAR (gstring, from + i)); + glyph = LGSTRING_GLYPH (gstring, from + i); + c = LGLYPH_CHAR (glyph); cmp_it->nbytes += CHAR_BYTES (c); - cmp_it->width += CHARACTER_WIDTH (c); + cmp_it->width += LGLYPH_WIDTH (glyph); } } return c; -- 2.53.0
0006-etc-NEWS-Document-improved-Emoji-support-on-textual-.patch
(text/x-diff, 1.4 KB)
From 2042315056defff267fcfbccacc651d09754a5d4 Mon Sep 17 00:00:00 2001 From: Kai Ma <[email protected]> Date: Sun, 9 Aug 2026 20:31:26 +0200 Subject: [PATCH 6/6] ; * etc/NEWS: Document improved Emoji support on textual terminals. --- etc/NEWS | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index 9ddf8d41028..e5ebcc6f413 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -107,6 +107,25 @@ Therefore, the instructions in the NEWS file for Emacs 30.1 to add to your initialization file no longer apply, and you may remove it. +--- +** Improved Emoji support on textual terminals +Emacs now composes Emoji sequences as two-column glyphs on textual +terminals when 'auto-composition-mode' is enabled. This should correct +a number of movement and display issues on an Emoji-capable terminal +emulator. + +However, the actual rendering of Emoji still depends on the capability +of the terminal emulator: + +- If the terminal emulator does not support grapheme clusters at all, + composed Emoji sequences will not work. In this case, disable + 'auto-composition-mode'. + +- Non-compliant terminal emulators may not always render an Emoji + sequence as a wide glyph, depending on the character width of the + first code point in the sequence. In this case, it might help to set + 'tty-display-emoji-force-wide' to nil. + * Editing Changes in Emacs 32.1 -- 2.53.0