Proportional fonts in Emacs

Last updated on August 11, 2023 by Jean-Philippe Bernardy
Tags: 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:

elastic-indent-mode: simple example

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.

elastic-indent-mode on itself

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.

Badly aligned keymap description