Using Fontsets in Emacs

Fontset?

Fontset is a feature of Emacs that allows you to bundle together multiple fonts and use them as a single font, such that it covers more characters than a single font could have. For example, you can combine a Latin font, a Greek font and a Chinese font together.

With fontsets, we can use different Unicode fonts for different faces. For example, serif Latin and Chinese font for a “serif” face, and sans serif Latin and Chinese font for a “sans” face. Without fontsets, we can only set different Latin fonts to faces and use a single fall-back Chinese font.

A graph showing different fonts with different faces

Create a fontset

A fontset is recognized by its name. Each fontset has two names, one short and one long. The short name looks like fontset-xxx. The long name is a X Logical Font Description with last two fields being fontset and xxx. For example,

-*-ibm plex mono-medium-*-*-*-13-*-*-*-*-*-fontset-my fontset

Emacs come with three fontsets by default: fontset-startup, fontset-standard and fontset-default. We only care about fontset-default; it is the ultimate fall-back when Emacs cannot find a font to display a character. But more on that later.

To create a fontset, you can use create-fontset-from-fontset-spec and pass it a bunch of X Logical Font Descriptions, each for a font you want to include. I find that tedious. Instead, I like to create a fontset with a single ASCII font and use set-fontset-font to add other fonts later, like this:

(create-fontset-from-fontset-spec
 (font-xlfd-name
  (font-spec :family "IBM Plex Mono"
             :size 13
             :registry "fontset-my fontset")))

Make sure you put the short fontset name under the :registry spec. The code above creates the fontset, and returns its long name,

-*-ibm plex mono-*-*-*-*-13-*-*-*-*-*-fontset-my fontset

Now we can add a Chinese font and a Greek font:

(set-fontset-font
 "fontset-my fontset"
 'han (font-spec :family "Source Han Serif" :size 12))
(set-fontset-font
 "fontset-my fontset"
 'greek (font-spec :family "Academica"))

If you are not familiar with set-fontset-font, Emacs, fonts and fontsets is a good read.

Apply a fonset

Although the manual says we can use a fontset wherever a font is appropriate, it is not entirely true. If you pass your fontset through the :font attribute in set-face-attribute, Emacs takes the ASCII font from the fontset and only uses the ASCII font for the face1. The real way to do it is to use the undocumented :fontset attribute:

(set-face-attribute
 'some-face nil :fontset "fontset-my fontset")

That’s not all. While the above code works for most faces, setting :fontset for default will not work as you expected, because Emacs again only takes the ASCII font, even if you use the fontset attribute2. So don’t set the fontset for the default face; instead, just modify fontset-default (it’s the ultimate fall-back fontset we mentioned earlier) for Unicode fonts, and use whatever method you like for ASCII font. If you read Emacs, fonts and fontsets, you’ll know we can modify fontset-default by either

(set-fontset-font "fontset-default" ...)

or

(set-fontset-font t ...)

Technically you could set the font attribute of a frame to a fontset by set-frame-font and it works fine. But as soon as you change any font-related attributes in default face, like font size, your fontset in the frame attribute will be overwritten by the font derived from default face. So the best way is still to just modify fontset-default.

According to the source.
Basically, if the face is default, set-face-attribute calls set_font_frame_param (source), which only looks at the :font attribute (source).

Further reading