1

Emacs 27 gained support for Cairo for font rendering and this is supposed to let me render emojis but I can't find out how to make it work. I just about managed to set up some fontsets but the emoji which are made out of multiple characters don't work. Is it possible to make them work?

2

Step 1 is to make sure that you have Cairo and Harfbuzz enabled. You can run this elisp and check the results to test this:

(featurep 'cairo) ; should evaluate to t
(frame-parameter (selected-frame) 'font-backend) ; should be a list starting with ftcrhb

I'm not sure about Windows or MacOS support. I think for Windows, you possibly need harfbuzz as the font backend and for MacOS you don't get any choice and I think things may work automatically.

Step 2 is to ensure you have a font which supports emoji, and that font is used for emoji by your fontset. I did this with an Emacs lisp function:

(defun init-my-font ()
  (set-face-font
   'default
   (font-spec
    :family "<your default font>"
    :size 10.3
    :weight 'normal
    :width 'normal
    :slant 'normal))
  (set-face-attribute 'default nil :height 103)
  ;; emoji font
  (set-fontset-font
   t 'symbol
   (font-spec
    :family "Noto Color Emoji"
    :size 10.3
    :weight 'normal
    :width 'normal
    :slant 'normal))
  ;; fallback font
  (set-fontset-font
   t nil
   (font-spec
    :family "DejaVu Sans Mono"
    :size 10.3
    :weight 'normal
    :width 'normal
    :slant 'normal)))

If you use Emacs without a daemon, I think it is sufficient to call this function in your init file by writing (init-my-font). But I use a daemon and it seems to require that you wait until you have a gui frame before setting up the fonts. I have this hook for that:

(add-hook
 'server-after-make-frame-hook
 (let (done)
   (lambda ()
     (unless done
       ;; still set done to true even if we hit a bug (otherwise we
       ;; can never open a frame to see the problem)
       (setq done t)
       (init-my-font)))))

Note that this snippet requires lexical scoping in your init file.

This should cause some emoji to be displayed but not the โ€œligaturesโ€ made of multiple emoji, for example country flags, skin tone modifiers, or family emoji. To support them, we need to specify what the sequences start with, a regex for the rest of the sequence, and an instruction for how Emacs should figure out which glyphs to draw. For the instruction we use font-shape-gstring, which tells Emacs to ask the font how to combine things (but the problem is knowing how Emacs decides which font from the fontset to use. I'm not really sure but I think it picks the font for the first character.) This config catches some things that won't form ligatures however that isn't a problemโ€”they just get shaped into multiple glyphs as they normally would.

;; setting up composition functions for emoji modifiers
(dolist (items `(((?๐Ÿ‡ฆ . ?๐Ÿ‡ฟ) [".[๐Ÿ‡ฆ-๐Ÿ‡ฟ]+" 0 font-shape-gstring])
                 ((?๐Ÿณ . ?๐Ÿด) [".[๏ธโ€๐ŸŒˆโšงโ˜ ๓ € -๓ ฟ]*" 0 font-shape-gstring])
                 (?โƒฃ ["[#*0-9]๏ธโƒฃ" 2 font-shape-gstring])
                 ;; TODO: I can't make keycap sequences work because I
                 ;; think they're trying to shape with the wrong font.
                 ,@(mapcar (lambda (range) (list range [".โ€?[๐Ÿป-๐Ÿฟ]?[โ€๏ธโ™‚โ™€]*๏ธ?" 0 font-shape-gstring]))
                           (concatenate 'list "โ˜๐ŸŽ…๐Ÿ‡๐Ÿ‘‚๐Ÿ‘ƒ๐Ÿ‘ฆ๐Ÿ‘ง๐Ÿ‘ผ๐Ÿ’๐Ÿ’‘๐Ÿ’ช๐Ÿ•ด๐Ÿ•ต๐Ÿ•บ๐Ÿ–๐Ÿ–•๐Ÿ––๐Ÿ™‡๐Ÿšฃ๐Ÿ›€๐Ÿ›Œ๐Ÿค๐Ÿคž๐ŸคŸ๐Ÿคฆ๐Ÿคฝ๐Ÿคพ๐Ÿฅท๐Ÿฆป๐Ÿ‘ฏโค"
                                        '((?โ›น . ?โœ) (?๐Ÿ‚ . ?๐Ÿ„) (?๐ŸŠ . ?๐ŸŒ) (?๐Ÿ‘† . ?๐Ÿ‘)
                                          (?๐Ÿ‘ซ . ?๐Ÿ‘ฎ) (?๐Ÿ‘ฐ . ?๐Ÿ‘ธ) (?๐Ÿ’ . ?๐Ÿ’‡) (?๐Ÿ™… . ?๐Ÿ™‡) (?๐Ÿ™‹ . ?๐Ÿ™)
                                          (?๐Ÿšด . ?๐Ÿšถ) (?๐Ÿค˜ . ?๐Ÿคœ) (?๐Ÿคฐ . ?๐Ÿคน) (?๐Ÿคผ . ?๐Ÿคพ) (?๐Ÿฆต . ?๐Ÿฆน)
                                          (?๐Ÿง . ?๐Ÿง) (?๐Ÿง’ . ?๐ŸงŸ))) )
                 (?๐Ÿง‘ [".โ€?[๐Ÿป-๐Ÿฟ]?[โ€โš•โš–โœˆโค๏ธ๐ŸŒพ๐Ÿณ๐Ÿผ๐ŸŽ„๐ŸŽ“๐ŸŽค๐ŸŽจ๐Ÿซ๐Ÿญ๐Ÿ‘ฆ-๐Ÿ‘ฉ๐Ÿ’‹๐Ÿ’ป๐Ÿ’ผ๐Ÿ”ง๐Ÿ”ฌ๐Ÿš€๐Ÿš’๐Ÿค๐Ÿฆฏ๐Ÿฆฐ-๐Ÿฆณ๐Ÿฆผ๐Ÿฆฝ๐Ÿง‘]*" 0 font-shape-gstring])
                 ((?๐Ÿ‘จ . ?๐Ÿ‘ฉ) [".โ€?[๐Ÿป-๐Ÿฟ]?[โ€โš•โš–โœˆโค๏ธ๐ŸŒพ๐Ÿณ๐Ÿผ๐ŸŽ„๐ŸŽ“๐ŸŽค๐ŸŽจ๐Ÿซ๐Ÿญ๐Ÿ‘ฆ-๐Ÿ‘ฉ๐Ÿ’‹๐Ÿ’ป๐Ÿ’ผ๐Ÿ”ง๐Ÿ”ฌ๐Ÿš€๐Ÿš’๐Ÿค๐Ÿฆฏ๐Ÿฆฐ-๐Ÿฆณ๐Ÿฆผ๐Ÿฆฝ๐Ÿง‘]*" 0 font-shape-gstring])
                 ,@(mapcar (lambda (str) (list (elt str 0) (vector str 0 'font-shape-gstring)))
                           '("๐Ÿ˜ถโ€๐ŸŒซ๏ธ" "๐Ÿˆโ€โฌ›" "๐Ÿ•โ€๐Ÿฆบ" "๐Ÿปโ€โ„๏ธ" "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ" "๐Ÿ˜ฎโ€๐Ÿ’จ" "๐Ÿ˜ตโ€๐Ÿ’ซ"))))
  (set-char-table-range
   composition-function-table
   (car items)
   (list (cadr items))))

Possibly, the above snippet will not survive StackExchangeโ€™s formatting, so here it is in base64 of UTF-8:

Ozsgc2V0dGluZyB1cCBjb21wb3NpdGlvbiBmdW5jdGlvbnMgZm9yIGVtb2ppIG1vZGlmaWVycwooZG9saXN0IChpdGVtcyBgKCgoP/Cfh6YgLiA/8J+HvykgWyIuW/Cfh6Yt8J+Hv10rIiAwIGZvbnQtc2hhcGUtZ3N0cmluZ10pCiAgICAgICAgICAgICAgICAgKCg/8J+PsyAuID/wn4+0KSBbIi5b77iP4oCN8J+MiOKap+KYoPOggKAt86CBv10qIiAwIGZvbnQtc2hhcGUtZ3N0cmluZ10pCiAgICAgICAgICAgICAgICAgKD/ig6MgWyJbIyowLTld77iP4oOjIiAyIGZvbnQtc2hhcGUtZ3N0cmluZ10pCiAgICAgICAgICAgICAgICAgOzsgVE9ETzogSSBjYW4ndCBtYWtlIGtleWNhcCBzZXF1ZW5jZXMgd29yayBiZWNhdXNlIEkKICAgICAgICAgICAgICAgICA7OyB0aGluayB0aGV5J3JlIHRyeWluZyB0byBzaGFwZSB3aXRoIHRoZSB3cm9uZyBmb250LgogICAgICAgICAgICAgICAgICxAKG1hcGNhciAobGFtYmRhIChyYW5nZSkgKGxpc3QgcmFuZ2UgWyIu4oCNP1vwn4+7LfCfj79dP1vigI3vuI/imYLimYBdKu+4jz8iIDAgZm9udC1zaGFwZS1nc3RyaW5nXSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgIChjb25jYXRlbmF0ZSAnbGlzdCAi4pid8J+OhfCfj4fwn5GC8J+Rg/Cfkabwn5Gn8J+RvPCfko/wn5KR8J+SqvCflbTwn5W18J+VuvCflpDwn5aV8J+WlvCfmYfwn5qj8J+bgPCfm4zwn6SP8J+knvCfpJ/wn6Sm8J+kvfCfpL7wn6W38J+mu/Cfka/inaQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnKCg/4pu5IC4gP+KcjSkgKD/wn4+CIC4gP/Cfj4QpICg/8J+PiiAuID/wn4+MKSAoP/CfkYYgLiA/8J+RkCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKD/wn5GrIC4gP/Cfka4pICg/8J+RsCAuID/wn5G4KSAoP/CfkoEgLiA/8J+ShykgKD/wn5mFIC4gP/CfmYcpICg/8J+ZiyAuID/wn5mPKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoP/CfmrQgLiA/8J+atikgKD/wn6SYIC4gP/CfpJwpICg/8J+ksCAuID/wn6S5KSAoP/CfpLwgLiA/8J+kvikgKD/wn6a1IC4gP/CfprkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICg/8J+njSAuID/wn6ePKSAoP/Cfp5IgLiA/8J+nnykpKSApCiAgICAgICAgICAgICAgICAgKD/wn6eRIFsiLuKAjT9b8J+Puy3wn4+/XT9b4oCN4pqV4pqW4pyI4p2k77iP8J+MvvCfjbPwn4288J+OhPCfjpPwn46k8J+OqPCfj6vwn4+t8J+Rpi3wn5Gp8J+Si/Cfkrvwn5K88J+Up/CflKzwn5qA8J+akvCfpJ3wn6av8J+msC3wn6az8J+mvPCfpr3wn6eRXSoiIDAgZm9udC1zaGFwZS1nc3RyaW5nXSkKICAgICAgICAgICAgICAgICAoKD/wn5GoIC4gP/CfkakpIFsiLuKAjT9b8J+Puy3wn4+/XT9b4oCN4pqV4pqW4pyI4p2k77iP8J+MvvCfjbPwn4288J+OhPCfjpPwn46k8J+OqPCfj6vwn4+t8J+Rpi3wn5Gp8J+Si/Cfkrvwn5K88J+Up/CflKzwn5qA8J+akvCfpJ3wn6av8J+msC3wn6az8J+mvPCfpr3wn6eRXSoiIDAgZm9udC1zaGFwZS1nc3RyaW5nXSkKICAgICAgICAgICAgICAgICAsQChtYXBjYXIgKGxhbWJkYSAoc3RyKSAobGlzdCAoZWx0IHN0ciAwKSAodmVjdG9yIHN0ciAwICdmb250LXNoYXBlLWdzdHJpbmcpKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgJygi8J+YtuKAjfCfjKvvuI8iICLwn5CI4oCN4qybIiAi8J+QleKAjfCfproiICLwn5C74oCN4p2E77iPIiAi8J+Rge+4j+KAjfCfl6jvuI8iICLwn5iu4oCN8J+SqCIgIvCfmLXigI3wn5KrIikpKSkKICAoc2V0LWNoYXItdGFibGUtcmFuZ2UKICAgY29tcG9zaXRpb24tZnVuY3Rpb24tdGFibGUKICAgKGNhciBpdGVtcykKICAgKGxpc3QgKGNhZHIgaXRlbXMpKSkpCg==

You can decode it with the command base64 -d.

2

You need to use a font with support for that. This is what I do:

(if (>= emacs-major-version 27)
    (set-fontset-font t '(#x1f000 . #x1faff)
              (font-spec :family "Noto Color Emoji")))
2
  • Does this work for country flags or skin tone modified emoji? – Dan Robertson Dec 11 '20 at 22:52
  • No, but it's simple. Ah, now I see that the question explicitly says "which are made out of multiple characters". I missed that, thinking it was a question just about getting emojis to work at all. – pst Dec 11 '20 at 23:27

Your Answer

By clicking โ€œPost Your Answerโ€, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.