Subject: bug#63731: [PATCH] Support Emoji Variation Sequence 16 (FE0F) where appropriate

From: Robert Pluim <[email-masked]>
Date: Mon, 05 Jun 2023 15:36:52 +0200

>>>>> On Mon, 05 Jun 2023 16:12:20 +0300, Eli Zaretskii <[email-masked]> said:

    >> From: Robert Pluim <[email-masked]>
    >> Cc: [email-masked],  [email-masked]
    >> Date: Mon, 05 Jun 2023 15:08:08 +0200
    >> 
    >> >>>>> On Sat, 03 Jun 2023 08:36:59 +0300, Eli Zaretskii <[email-masked]> said:
    >> 
    >> >> Sequence       Font             Result
    >> >> 23e9 fe0e      system           black box
    >> >> 23e9 fe0e      Symbola          correct text representation
    >> >> 23e9 fe0e      NotoEmoji        correct text representation
    >> >> 23e9 fe0e      NotoColorEmoji   blank
    >> >> 
    >> >> And on emacs-29, Symbola and NotoEmoji compose that sequence
    >> >> correctly. Now I just need to persuade emacs-30 to use one of them.
    >> 
    Eli> So you are saying that, in our default fontset, we should specify that
    Eli> #xFE0E should be displayed by Noto Emoji (with Symbola as fallback),
    Eli> and then make sure that font_range uses the same font for the likes of
    Eli> #x23E9?  IOW, specify a different font for VS-15 even though is script
    Eli> is 'emoji'?
    >> 
    >> Yes, that works (and we can remove VS-15 and VS-16 from the emoji
    >> script, so that theyʼll then be displayed via
    >> `glyphless-char-display-control' when theyʼre on their own).

    Eli> What about the rest of VS-nn? do they need to stay in 'emoji' script,
    Eli> and if so, why?

They were never in the 'emoji' script anyway.

    >> Thanks for the suggestion Eli, I was looking at it from the wrong
    >> direction.

    Eli> You are the one who did most of the footwork, so kudos to you.

    Eli> This is simple enough to install on emacs-29, I think?

The main change is in font.c, and looks like this. I think itʼs too
big for emacs-29 (breaking composition is very easy, itʼs entirely
possible Iʼve missed a few cases :-) )

diff --git a/src/font.c b/src/font.c
index e586277a5d3..30b088c818e 100644
--- a/src/font.c
+++ b/src/font.c
@@ -3633,10 +3633,14 @@ font_at (int c, ptrdiff_t pos, struct face *face, struct window *w,
 /* Check if CH is a codepoint for which we should attempt to use the
    emoji font, even if the codepoint itself has Emoji_Presentation =
    No.  Vauto_composition_emoji_eligible_codepoints is filled in for
-   us by admin/unidata/emoji-zwj.awk.  */
+   us by admin/unidata/emoji-zwj.awk.  We also check if there's a
+   VS-15 or VS-16 following CH, and select text/emoji presentation
+   respectively if so.  */
 static bool
-codepoint_is_emoji_eligible (int ch)
+codepoint_is_font_change_eligible (int ch, int next_c)
 {
+  if (next_c == 0xFE0E || next_c == 0xFE0F)
+    return true;
   if (EQ (CHAR_TABLE_REF (Vchar_script_table, ch), Qemoji))
     return true;
 
@@ -3690,21 +3694,43 @@ font_range (ptrdiff_t pos, ptrdiff_t pos_byte, ptrdiff_t *limit,
 	}
       face = FACE_FROM_ID (f, face_id);
     }
-
-  /* If the composition was triggered by an emoji, use a character
-     from 'script-representative-chars', rather than the first
-     character in the string, to determine the font to use.  */
-  if (codepoint_is_emoji_eligible (ch))
+  int next_c = 0;
+  {
+    ptrdiff_t p = pos;
+    ptrdiff_t p_b = pos_byte;
+    int c;
+    c = (NILP (string)
+	 ? fetch_char_advance_no_check (&p, &p_b)
+	 : fetch_string_char_advance_no_check (string, &p, &p_b));
+    if (p < *limit)
+      {
+	c = (NILP (string)
+	     ? fetch_char_advance_no_check (&p, &p_b)
+	     : fetch_string_char_advance_no_check (string, &p, &p_b));
+	next_c = c;
+      }
+  }
+  if (codepoint_is_font_change_eligible (ch, next_c))
     {
-      Lisp_Object val = assq_no_quit (Qemoji, Vscript_representative_chars);
-      if (CONSP (val))
+      if (next_c == 0xFE0E)
 	{
-	  val = XCDR (val);
+	  font_object = font_for_char (face, 0xFE0E, pos, string);
+	}
+      else
+	{
+	  /* If the composition was triggered by an emoji, use a character
+	     from 'script-representative-chars', rather than the first
+	     character in the string, to determine the font to use.  */
+	  Lisp_Object val = assq_no_quit (Qemoji, Vscript_representative_chars);
 	  if (CONSP (val))
-	    val = XCAR (val);
-	  else if (VECTORP (val))
-	    val = AREF (val, 0);
-	  font_object = font_for_char (face, XFIXNAT (val), pos, string);
+	    {
+	      val = XCDR (val);
+	      if (CONSP (val))
+		val = XCAR (val);
+	      else if (VECTORP (val))
+		val = AREF (val, 0);
+	      font_object = font_for_char (face, XFIXNAT (val), pos, string);
+	    }
 	}
     }

Robert
--