Proportional fonts in Emacs
This article aims to be a definitive reference for the use of proportional fonts in Emacs, with emphasis on programming. In Emacs proportional fonts are called variable-pitch faces. (More pedantry can go here, but I’ll spare myself from writing it.) Regardless, I’m going to call fonts whose characters do not have a fixed width proportional fonts. Non-proportional fonts are also called monospace fonts. I’ll discuss why we want them, and how well Emacs supports them.
Why proportional fonts?
Simply put, proportional fonts are better than monospace fonts, even for programming. This is the case at least for Latin or Greek scripts and mathematical symbols, which is what I’m using. This fact partly due to the greater liberty that proportional fonts allow. Speaking from personal experience, I invariably feel more comfortable reading text typeset in proportional fonts rather than in monospace fonts. Besides, proportional fonts tend to use less horizontal space than monospace fonts, saving precious screen space.
However there can be reasons for using monospace fonts. Such reasons are originally only technical. In particular, early printers and terminals could only support a fixed grid of characters. Such devices are not in common use nowadays, but they influenced software strongly enough that the monospace assumption persists to this day in many contexts. In sum, there is a cultural bias for using monospace fonts, in particular for programming. I want to experiment and ignore this cultural bias as much as possible. Emacs being an eminently customizable piece of software, I want to investigate how far I can push the use of proportional fonts, and report where I face technical limitations.
Successes
Graphical Emacs backends support proportional fonts alright, so we’re already a long way there.
Programming
We face the problem that programmers very often take advantage of monospace fonts to align code, typically by padding code with extra spaces (or tabs). There are two classes of alignments: alignment relative to the start of the line (widely known as indentation), and what will call tabular alignment.
Indentation
The problem of indenting with proportional fonts is that the space
character is typically narrower than most other
characters. Consequently, an indented line can appear to have a lower
indentation than it actually has. An idea is to use a font with large enough spaces, but even then lines will not line up exactly. This
isn’t a huge problem if indentation needs only be correct relative to
other indented lines, but it becomes very significant if indentation
must be correct relatively to a non-indented line. For instance,
arg1
, arg2
and arg3
should are lined up in the example below,
with a monospace font, but they won’t be with a proportional one.
some_long_function_name_with_many_wide_characters(arg1,
arg2,
arg3)
This sort of alignment convention is found in many languages. For instance it pervasive in lisps. In some languages, such as Haskell, it is even meaningful for the compiler. So it must be taken care of. Fortunately, there is an solution to this issue. It suffices to set the width of space characters belonging to the indentation to be the same as the width of the character directly above them, like so:
It is possible to implement this because Emacs provides means to query the pixel size of displayed characters. Yay! Some further complication happens because indentation is sometimes implemented by tabulation characters. The solution is to treat each tabulation character as the number of spaces that it would stand for in a monospace context.
This idea was proposed and implemented by Scott Messick I added support for tabs, optimized it and added support for indentation guides, resulting in the elastic-modes package. This package also supports tabular alignment, and can also be configured to show indentation guides.
Tabular alignment
The second kind of alignment is tables, such as:
some_variable = value1
some_other_variable = value2
a_third_variable = value3
Unfortunately, the previous solution does not apply, because each line involve different non-space characters in general. Fortunately, this kind of spacing is typically non-significant, even in Haskell. So, for this kind of application, it is possible to use elastic tabstops. The idea is to use tabulation characters to separate column in the table, and let the editor lineup the columns. (The link above shows elastic tabstops in action.) A pleasant characteristic of this convention is that it one of the original uses of tabstops on typewriters. Consequently, in some situations the table may be displayed correctly in an editor which uses plain tabstops and a monospace font.
Org-mode
Org mode is mostly for inputting free-form text, and as such does not suffer from much cultural bias towards monospace. You will however want to configure Emacs to use a monospace font for certain elements, such as check-boxes and tables (See below).
Gold medals
Some packages already have special support for proportional fonts. I’d love it if this is the beginning of a trend and it is expected that packages are written with proportional fonts in mind.
Vertico
The Vertico package has special support for variable pitch fonts: it correctly aligns the annotations provided by marginalia!
Info
The standard info
package correctly aligns menus.
Bugs, workarounds and shortcomings
Bug in window-text-pixel-size
The technique to query the size of a character (or any text) in pixels
involves using window-text-pixel-size
. Unfortunately it is buggy in
Emacs 28 and 29.1. One of the problem is witnessed by the following example.
Copy it in a scratch elisp file and evaluate the last line:
;m some text here
;x
;^--- 2nd character of following lines have wrong
; window-text-pixel-size if measured on their own.
;;note presence of NARROW NO-BREAK SPACE in the 1st line
(window-text-pixel-size nil 19 20) ; returns (0 . 27)
Fortunately, as far as I can tell, this bug can always be worked around by computing the size of multiple-character regions which overlap and subtract them.
Bug in org-mode tables
Tables do not work with proportional fonts because org-mode automatically pads them with several spaces. At the time of writing, the algorithm which does this appears to assume a monospace font, and thus computes incorrect results for proportional fonts.
Unfortunately, the bug goes deeper. Indeed, if you set your default font to proportional, but set the table font to be monospace, you get wrong results. This appears to be because org-mode attempts to deal with double-width characters. If you don’t use those, then the following workaround seems to fix the problem:
(fset 'org-string-width 'org--string-width-1)
Shortcomings
The following packages and functions would benefit from special support for proportional fonts. This is not an exhaustive list (yet), just a few things that I personally use or that I was made aware of.
org-mode
code blocks can be configured to be fontified “natively”. This means that the normal major mode for the code block will be used for fontification. org-mode does this by creating a temporary buffer with the contents of the block, and set the appropriate major mode. Unfortunately it does so at each keystroke, and the process of computing elastic indentation is too slow for that. (Currently it is done somewhat lazily.) So, it is currently recommended to use a monospace font in org-mode source blocks.marginalia
: if the marginal data is multi field, then the corresponding columns are not aligned. (See for exampledescribe-command
with marginalia enabled.)which-key
shows incorrectly aligned columnshelm
shows incorrectly aligned columnsdired
shows incorrectly aligned columns. (But it’s unclear if it can adapt at all to proportional fonts.)describe-face
shows incorrectly aligned columns- In the Emacs manual, the description of keymaps are not aligned properly.