Variable-width Serif Fonts when editing plain text in Emacs

Published: July 06, 2015

I edit a lot of \(\LaTeX\) documents using AUCTeX in Emacs. Since I believe that serif fonts like Times New Roman are a lot easier to read than monospaced fonts, I wanted to be able to view and edit text in an emacs buffer using a variable-width, serif font. After 20 minutes of elisp hacking during lunch, I came up with toggle-serif, a small elisp function that does exactly what I want.

When invoked, toggle-serif changes the default font family of the buffer to Liberation Serif. The only problem I had was that I wanted to preserve the default monospace fonts for some faces, for example verbatim code snippets or markup commands.

For this reason, toggle-serif keeps the default font family for every font that is listed in the variable serif-preserve-default-list. To set this variable, we need code like the following in our emacs configuration:

(defvar serif-preserve-default-list nil
  "A list holding the faces that preserve the default family and
  height when TOGGLE-SERIF is used.")
(setq serif-preserve-default-list
      '(;; LaTeX markup
        font-latex-math-face
        font-latex-sedate-face
        font-latex-warning-face
        ;; org markup
        org-latex-and-related
        org-meta-line
        org-verbatim
        org-block-begin-line
        ;; syntax highlighting using font-lock
        font-lock-builtin-face
        font-lock-comment-delimiter-face
        font-lock-comment-face
        font-lock-constant-face
        font-lock-doc-face
        font-lock-function-name-face
        font-lock-keyword-face
        font-lock-negation-char-face
        font-lock-preprocessor-face
        font-lock-regexp-grouping-backslash
        font-lock-regexp-grouping-construct
        font-lock-string-face
        font-lock-type-face
        font-lock-variable-name-face
        font-lock-warning-face))

After specifying the faces we want to preserve, we can use toggle-serif:

(defun toggle-serif ()
  "Change the default face of the current buffer to use a serif family."
  (interactive)
  (when (display-graphic-p)  ;; this is only for graphical emacs
    ;; the serif font familiy and height, save the default attributes
    (let ((serif-fam "Liberation Serif")
          (serif-height 120)
          (default-fam (face-attribute 'default :family))
          (default-height (face-attribute 'default :height)))
      (if (not (bound-and-true-p default-cookie))
          (progn (make-local-variable 'default-cookie)
                 (make-local-variable 'preserve-default-cookies-list)
                 (setq preserve-default-cookies-list nil)
                 ;; remap default face to serif
                 (setq default-cookie
                       (face-remap-add-relative
                        'default :family serif-fam :height serif-height))
                 ;; keep previously defined monospace fonts the same
                 (dolist (face serif-preserve-default-list)
                   (add-to-list 'preserve-default-cookies-list
                                (face-remap-add-relative
                                 face :family default-fam :height default-height)))
                 (message "Turned on serif writing font."))
        ;; undo changes
        (progn (face-remap-remove-relative default-cookie)
               (dolist (cookie preserve-default-cookies-list)
                 (face-remap-remove-relative cookie))
               (setq default-cookie nil)
               (setq preserve-default-cookies-list nil)
               (message "Restored default fonts."))))))

The end result is pretty cool: Regular text is shown using the serif font, while code snippets and markup are still using the default monospaced font. A picture is worth a thousand words, so here is a screenshot of me writing this blog post using markdown-mode:

serif_small.png

Another small battle in the fight against word processors seems won…

Dennis