Faces

We can play a few cool tricks with different faces in Emacs. This is mainly interesting in graphical emacs. This file holds most of the faces-related configuration.

Defaults

The following variables (when set in, i.e., ~/.emacs-site.el) let us override the face attributes for some faces:

  • do.faces/default-face-attrs
  • do.faces/comment-face-attrs
  • do.faces/sans-face-attrs
  • do.faces/serif-face-attrs
(defvar do.faces/serif-face-attrs nil
  "An optional list of face attributes for the serif face used in
  the `toggle-serif' function.")

(defvar do.faces/sans-face-attrs nil
  "An optional list of face attributes for the sans face used in
  the `sans-font' function.")

(defvar do.faces/default-face-attrs nil
  "An optional list of face attributes for the default face.")

(defvar do.faces/default-face-attrs nil
  "An optional list of face attributes for the comment face.")

This next block sets up the default face and the comment face

(defun do.faces/set-face-attrs-global (face attrs)
  "Set all attributes of FACE to as defined in ATTRS."
  (apply #'set-face-attribute (append `(,face nil) attrs)))

(when (display-graphic-p)
  (let ((default-attrs (if (bound-and-true-p do.faces/default-face-attrs)
                           do.faces/default-face-attrs
                         (list :family "DejaVu Sans Mono"
                               :height 120)))
        (comment-attrs (if (bound-and-true-p do.faces/comment-face-attrs)
                           do.faces/comment-face-attrs
                         (list :slant 'italic
                               :weight 'semibold))))
    ;; configure the default and comment face
    (do.faces/set-face-attrs-global 'default default-attrs)
    (do.faces/set-face-attrs-global 'font-lock-comment-face comment-attrs)
    ;; make doc face less offensive
    (setq font-lock-doc-face font-lock-comment-face)))

Minibuffer

(set-face-attribute 'minibuffer-prompt nil
                    :weight 'bold
                    :slant 'italic
                    :foreground (face-attribute 'font-lock-keyword-face :foreground))

Serif font for text

I want to be able to toggle a variable-width "writing font" with serifs for a buffer in which I'm mainly composing text. This is particularly comfortable when editing \(\LaTeX\) or org-mode source files.

First, whenever we turn on the serif font, there could be faces where we want to preserve the monospace font (usually all of the markup commands and math blocks). These should be listed in the variable do.faces/serif-preserve-defaults-list.

(defvar do.faces/serif-preserve-defaults-list nil
  "A list holding the faces that preserve the default family and
  height when TOGGLE-SERIF is used.")
(setq do.faces/serif-preserve-defaults-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
        org-block-end-line
        org-block
        org-property-value
        ;; 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))

The following interactive function can then be used to toggle the faces of the current buffer to a mix of serif and monospace fonts. Remember, the face that is used for serifs is specified using do.faces/serif-face-attrs.

(defun do.util/get-plist-keys (plist &rest k)
  "Return a list of all the keys of plist PLIST."
  (if plist (do.util/get-plist-keys (cddr plist) (append (car k) (list (car plist))))
    (car k)))

(defun toggle-serif (&optional show-echo)
  "Change the default face of the current buffer to the serif
face specified by `do.faces/serif-face-attrs'."
  (interactive)
  (when (display-graphic-p)  ;; this is only for graphical emacs
    ;; the serif font familiy and height, save the default attributes
    (let* ((serif-attrs (if (bound-and-true-p do.faces/serif-face-attrs)
                            do.faces/serif-face-attrs
                          (list :family "Source Serif Pro"
                                :height (round (* 1.2 (face-attribute 'default :height))))))
           (default-attrs (mapcan #'(lambda (key) (list key (face-attribute 'default key)))
                                  (do.util/get-plist-keys serif-attrs))))
      (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 (apply #'face-remap-add-relative (cons 'default serif-attrs)))
                 ;; keep previously defined monospace fonts the same
                 (dolist (face do.faces/serif-preserve-defaults-list)
                   (add-to-list 'preserve-default-cookies-list
                                (apply #'face-remap-add-relative (cons face default-attrs))))
                 (when show-echo (message "Turned on serif writing font.")))
        ;; else 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)
               (when show-echo (message "Restored default fonts.")))))))

Sans-serif font for code

Sometimes I just want to write code with a variable-width Sans Serif font. This function is very similar to the one above. the only difference here is that we are not doing the re-mapping business. Instead, every face gets remapped to the one specified in do.faces/sans-face-attrs.

(defun sans-font ()
  "Change the default face of this buffer to use the sans face
specified by `do.faces/sans-face-attrs'."
  (interactive)
  (when (display-graphic-p)
    (let ((sans-attrs (if (bound-and-true-p do.faces/sans-face-attrs)
                          do.faces/sans-face-attrs
                        (list :family "Fira Sans"
                              :height (round (* 1.1 (face-attribute 'default :height)))
                              :weight 'bold))))
      (if (not (bound-and-true-p default-cookie-sans))
          ;; remap default face to serif
          (progn (make-local-variable 'default-cookie-sans)
                 (setq default-cookie-sans
                       (apply #'face-remap-add-relative (cons 'default do.faces/sans-face-attrs))))
        ;; else undo changes
        (progn (face-remap-remove-relative default-cookie-sans)
               (setq default-cookie-sans nil)
               (message "Restored default fonts."))))))