I am trying to create a robust function to print the current buffer from Emacs on macOS (Sonoma+). My goal is to get a printout with:
- Syntax highlighting (color).
- Correct rendering for Japanese (CJK) characters.
- A pure white background (even though I use a dark theme).
- 2-in-1 page layout.
After many attempts, I've concluded that the most reliable approach is to temporarily switch to a minimal, white-background theme just before printing, and then switch back to my original dark theme after the print job is sent.
My Attempt
I wrote the following function to achieve this. The logic is:
- Define a minimal set of faces for a "print theme" with a white background.
- Save the user's currently enabled themes.
- Use unwind-protect to ensure the original theme is always restored.
- In the try block: disable the current theme, apply the temporary print faces using custom-theme-set-faces, and then run my printing pipeline (which uses htmlize and headless Chrome to generate and print a PDF).
- In the finally block: unload the temporary print faces and re-enable the user's original themes.
The Problematic Code
Here is the function I tried to implement:
(defun print-buffer-as-light-theme () "A command to print the buffer by temporarily applying a minimal light theme." (interactive) (let* ((print-theme-faces `((default ((t (:background "white" :foreground "black")))) (font-lock-comment-face ((t (:foreground "gray50")))) (font-lock-keyword-face ((t (:foreground "blue")))) (font-lock-string-face ((t (:foreground "purple")))) (font-lock-function-name-face ((t (:foreground "dark green")))) (font-lock-variable-name-face ((t (:foreground "saddle brown")))) (font-lock-type-face ((t (:foreground "teal")))) (font-lock-constant-face ((t (:foreground "dark violet")))))) (current-enabled-themes custom-enabled-themes)) (unwind-protect ;; --- TRY block: The main printing logic --- (progn (message "Applying temporary print theme...") (mapc #'disable-theme current-enabled-themes) (custom-theme-set-faces 'print-theme-temporary print-theme-faces) ;; The printing pipeline itself (which works fine on its own) (let* ((html-buffer (htmlize-buffer)) (html-content (with-current-buffer html-buffer (buffer-string))) (temp-html-file (make-temp-file "emacs-print-" nil ".html")) (temp-pdf-file (make-temp-file "emacs-print-" nil ".pdf")) (chrome-path "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome") (lpr-command (format "lpr -o number-up=2 '%s'" temp-pdf-file))) (kill-buffer html-buffer) (unwind-protect (progn (with-temp-file temp-html-file (insert html-content)) (shell-command (format "'%s' --headless --print-to-pdf='%s' --no-pdf-header-footer 'file://%s'" chrome-path temp-pdf-file temp-html-file)) (shell-command lpr-command)) (when (file-exists-p temp-html-file) (delete-file temp-html-file)) (when (file-exists-p temp-pdf-file) (delete-file temp-pdf-file))) (message "Print job sent."))) ;; --- FINALLY block: Restore the original theme --- (progn (message "Restoring original theme...") ;; Unload the temporary print faces (custom-theme-set-faces 'print-theme-temporary) ;; Re-enable the original themes (mapc #'enable-theme current-enabled-themes))))) The Error
When I run this function (M-x print-buffer-as-light-theme), it fails with the following error in the minibuffer:
Unknown theme 'print-theme-temporary'
This error seems to be triggered by the (custom-theme-set-faces 'print-theme-temporary) call in the cleanup (finally) block. It appears that applying faces with custom-theme-set-faces does not register a "theme" that can be unloaded by calling the same function with only the theme name.
My Question
What is the correct Emacs Lisp idiom to achieve this "temporary theme for printing" pattern? Specifically, how can I reliably apply a temporary set of faces, execute a command, and then guarantee that the original user theme is restored, without causing an "Unknown theme" error?
