4

I want to use a variable pitch face when using org mode (for composing articles, prose, etc.) The emacs wiki suggested using buffer face mode with a hook to set the face for org mode buffers:

 ;; Use variable width font faces in certain buffers
 (defun my-buffer-face-mode-variable ()
   "Set font to a variable width (proportional) fonts in current buffer"
   (interactive)
   (setq buffer-face-mode-face '(:family "Liberation Serif" ))
   (buffer-face-mode))

 ;; Set default font faces for org  modes
 (add-hook 'org-mode-hook 'my-buffer-face-mode-variable)

This correctly changes the buffer face to variable-width, but seems to break org mode indentation.

Without buffer face mode, the buffer uses the default fixed pitch face for text and a variable pitch face for headers (as defined by the theme.) The resulting indentation is correct:

enter image description here

With buffer face mode both text and header use variable pitch face, but breaks indentation of all but the first line of headers:

enter image description here

Perhaps there is a conflict using variable pitch faces with indentation or with visual line mode, which I also use?

 (with-eval-after-load 'org       
  (setq org-startup-indented t) 
  (add-hook 'org-mode-hook #'visual-line-mode))

2 Answers 2

3

I had the same issue. I believe this is because the indentation is implemented as a number of spaces, but those spaces are in the face "org-indent" which, by default, changes no attributes.

The result of this is that your indentation is set by the pitch of your "default" face, which is now narrower than the pitch of your bullets in your headings and therefore your body text looks like it's shifted left compared with the indentation of your headings.

As a solution I have the following in my init:

(set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))

My heading face always inherits from fixed-pitch so that tags line up, hence the use of "fixed-pitch" above.

It's not perfect, but this has at least made it acceptable enough that I've stopped noticing. I also use "org-bullets" for prettier indents, which may be why it doesn't work perfectly (since the bullets end up being of a varying size), but if you don't use org-bullets this is likely to work even better for you.

Note that I had to wrap the above face change into a hook to ensure it was done at the appropriate time. It's probably clumsy, but the way I've done that in my init is as follows:

(add-hook 'org-mode-hook
          (lambda ()
            (set-face-attribute 'org-indent nil
                                :inherit '(org-hide fixed-pitch))))
1

The answer by Stuart works as long as a space with the face fixed-pitch is as wide as the visible star of the heading and as wide the space after it (this is for example the case if the heading has face fixed-pitch and no unusally-wide unicode characters are used to display the stars), and provided one also sets face org-hide to inherit fixed-pitch:

(add-hook 'org-mode-hook
          (lambda ()
            (set-face-attribute 'org-indent nil
                                :inherit '(org-hide fixed-pitch))
            (set-face-attribute 'org-hide nil
                                :inherit 'fixed-pitch)))

A more general solution, which also works if the heading as well as the entry text use a variable-width font is the following:

(with-eval-after-load 'org

  (defun my-org-get-level-face (n)
    "Get the right face for match N in font-lock matching of headlines.
Modifies the orginal to make the font of the last star and the space
after it also inherit the fixed-pitch font."
    (let* ((org-l0 (- (match-end 2) (match-beginning 1) 1))
       (org-l (if org-odd-levels-only (1+ (/ org-l0 2)) org-l0))
       (org-f (if org-cycle-level-faces
              (nth (% (1- org-l) org-n-level-faces) org-level-faces)
            (nth (1- (min org-l org-n-level-faces)) org-level-faces))))
      (cond
       ((eq n 1) (if org-hide-leading-stars 'org-hide org-f))
       ((eq n 2) `((t :inherit (fixed-pitch ,org-f)))) ;; changed here only
       (t (unless org-level-color-stars-only org-f)))))

  (advice-add 'org-get-level-face :override #'my-org-get-level-face))

(add-hook 'org-mode-hook
          (lambda () 
            (set-face-attribute 'org-hide nil :inherit 'fixed-pitch)))

This works as long as the unicode characters used for the stars of the heading are as wide as a character in the fixed-pitch font. "*" works for example, but "◉" not. If you're using org-modern this unfortunately means you'll have to customize org-modern-star to use more boring symbols or disable the prettifying of bullets altogether. Similarly for org-superstar-headline-bullets-list if you're using org-superstar-mode (which comes with Doom), org-bullets-bullet-list if you're using org-bullets, etc. An even more general solution would be great!


Edit: The above works, but face-attribute expects a symbol for its face argument, so if you run any code that reads the face from the final star and then tries to pass it to face-attribute you'll get an error. A more proper solution is hence:

(with-eval-after-load 'org

  (defface org-level-1-stars '((t :inherit (fixed-pitch org-level-1)))
    "Face used for level 1 headline stars."
    :group 'org-faces)

  (defface org-level-2-stars '((t :inherit (fixed-pitch org-level-2)))
    "Face used for level 2 headline stars."
    :group 'org-faces)

  (defface org-level-3-stars '((t :inherit (fixed-pitch org-level-3)))
    "Face used for level 3 headline stars."
    :group 'org-faces)

  (defface org-level-4-stars '((t :inherit (fixed-pitch org-level-4)))
    "Face used for level 4 headline stars."
    :group 'org-faces)

  (defface org-level-5-stars '((t :inherit (fixed-pitch org-level-5)))
    "Face used for level 5 headline stars."
    :group 'org-faces)

  (defface org-level-6-stars '((t :inherit (fixed-pitch org-level-6)))
    "Face used for level 6 headline stars."
    :group 'org-faces)

  (defface org-level-7-stars '((t :inherit (fixed-pitch org-level-7)))
    "Face used for level 7 headline stars."
    :group 'org-faces)

  (defface org-level-8-stars '((t :inherit (fixed-pitch org-level-8)))
    "Face used for level 8 headline stars."
    :group 'org-faces)

  (defconst org-level-stars-faces
    '(org-level-1-stars org-level-2-stars org-level-3-stars org-level-4-stars
                        org-level-5-stars org-level-6-stars org-level-7-stars
                        org-level-8-stars))
  
  (defun my-org-get-level-face (n)
    "Get the right face for match N in font-lock matching of headlines.
Modifies the orginal to assign separate faces org-level-*-stars to the
stars and the space after."
    (let* ((org-l0 (- (match-end 2) (match-beginning 1) 1))
           (org-l (if org-odd-levels-only (1+ (/ org-l0 2)) org-l0))
           (org-i (if org-cycle-level-faces
                      (% (1- org-l) org-n-level-faces)
                    (1- (min org-l org-n-level-faces))))
           (org-f (nth org-i org-level-faces))
           (org-s (nth org-i org-level-stars-faces)))
      (cond
       ((eq n 1) (if org-hide-leading-stars 'org-hide org-s))
       ((eq n 2) org-s)
       (t (unless org-level-color-stars-only org-f)))))

  (advice-add 'org-get-level-face :override #'my-org-get-level-face))

(add-hook 'org-mode-hook
          (lambda () 
            (set-face-attribute 'org-hide nil :inherit 'fixed-pitch)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.