Werden wir Helden für einen Tag

Home | About | Archive

Advent of emacs #22: How I do ebook reading in emacs

Posted on Dec 22, 2022 by Chung-hong Chan

Welcome to another episode of “A Poseur Wandering the emacs lisp World”, I am your host, chainsawriot. Today I will give another example of programming using emacs lisp to extend emacs.

Example 2: minimalize nov

Like many poseurs, I read. Also like a lot of enemies of free software, I buy and read many DRM-ed ebooks with my proprietary, making-Jeff-Bezos-rich Amazon Kindle.

For technical books, I usually buy them from Humble Bundle. I usually get a dozen of books for less than 20 euros. The books are not DRM-protected and usually EPUB files are available. Similarly, I got free books also from Project Gutenberg, e.g. this book I am using to learn Latin. Similarly, the books are in free-as-free-speech EPUB.

nov by Vasilij Schneidermann et al is a EPUB reading mode for emacs. While emacs is a good editor, it can be a decent document viewer too. I have talked about GNU TexInfo on day 8.

  (use-package nov
    :config
    (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
    )

By default, nov displays an EPUB file like this:

Not bad. But not great. Comme si comme ça. I want to have a kind-of immersive reading experience. It still looks quite like, well, emacs.

Take 1: Following the README

The README file of nov actually provides instructions to improve the reading experience. Basically, it adjusts the fonts and text width (using visual-fill-column mode).

(defun my-nov-font-setup ()
  (face-remap-add-relative 'variable-pitch :family "Liberation Serif"
                                           :height 1.0))
(add-hook 'nov-mode-hook 'my-nov-font-setup)
(setq nov-text-width 80) 
(setq nov-text-width t)
(setq visual-fill-column-center-text t)
(add-hook 'nov-mode-hook 'visual-line-mode)
(add-hook 'nov-mode-hook 'visual-fill-column-mode)

Much better. But can I push it even further? In general, the README teaches me to create a hook function (my-nov-font-setup) and put it into nov-mode-hook (see day 17). So, I should be able to add more things into this hook function.

Take 2: Disable, disable, disable

What I wanted to do is to disable all the information display, specifically modeline, the header, the scroll bar, and even the cursor.

(defun nov-display ()
  (face-remap-add-relative 'variable-pitch :family "Liberation Serif"
						   :height 1.5)
  (toggle-scroll-bar -1)
  (setq mode-line-format nil
		nov-header-line-format ""
		cursor-type nil))
(setq nov-text-width 120)

Much more immersive. But disabling the cursor creates a problem: I sometimes need the cursor to interacting with the TOC and even to copy save content into the kill ring. I need to have a way to toggle the cursor.

Take 3: Toggling the cursor

(defvar nov-cursor nil "Whether the cursor is enabled")

(defun toggle-nov-cursor ()
  "Toggle nov cursor mode"
  (interactive)
  (if nov-cursor
	  (progn
		(setq cursor-type nil
			  nov-cursor nil)
		(scroll-lock-mode 1))
    (progn
	  (setq cursor-type t
			nov-cursor t)
	  (scroll-lock-mode -1)
	  )))

(defun nov-display ()
  (face-remap-add-relative 'variable-pitch :family "Liberation Serif"
						   :height 1.5)
  (scroll-lock-mode 1)
  (toggle-scroll-bar -1)
  (setq mode-line-format nil
		nov-header-line-format ""
		cursor-type nil))
(use-package visual-fill-column
  :config
  (setq-default visual-fill-column-center-text t)
  (setq-default visual-fill-column-width 120))
(use-package nov
  :config
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
  (add-hook 'nov-mode-hook 'nov-display)
  (add-hook 'nov-mode-hook 'visual-fill-column-mode)
  :bind
  (
   :map nov-mode-map 
		("C-q" . 'toggle-nov-cursor))
  )

And after this wall of emacs lisp, I can enable and disable the cursor by pressing C-q. In the following demo, I switch off the dark mode and also switch off the mode line of StumpWM. It’s almost like a full screen Kindle.

Now, this is the 3rd time I talked about C-q. I will explain it tomorrow.


Powered by Jekyll and profdr theme, a fork of true minimal theme