diff --git a/README.md b/README.md index 4060e18..db772d5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,137 @@ # novel-mode Emacs Screen Reader + +# Demo + +![demo](https://raw.githubusercontent.com/TLINDEN/novel-mode/master/demo.gif) + +# Introduction + +Novel mode is a minor mode which converts emacs into a screen +reader, or in other words, it enables distraction free reading. It +is however not suited for distraction free editing. Try writeroom +mode if you're looking for this. + +When turned on, it does the following conversions: + + - disable almost all distractions, as menu, toolbar, scrollbar + - enlarge font size + - switch to variable width font + - enable word wrap (without fringe marker) + - increate line spacing + - adds a window margin to the left and right (thereby centering the text) + - disable all input keys (rendering the buffer read-only) + - disable the cursor + - switch to buffer-scrolling (like e.g. in Acroread) + - display current reading position in percent + - add a couple of convenience one-key commands + +Novel mode provides the following one-key commands, when active: + + n scroll one page down + p scroll one page up + scroll one line down + scroll one line up + mousewheel scroll linewise up or down + SPC scroll one page down + increase margins, makes visible text narrower + decrease margins, makes visible text wider + + increase font size + - decrease font size + i invert video display + q quit novel mode + ? display key mapping + +Important: while normal key input (beside the ones listed above), +is disabled, Control and Meta still work, of course. Please be also +aware that this mode might conflict with god-mode or evil-mode. + +If you use this mode quite often, then it might be a good idea to +use save-place mode, so that a text file will be opened where you +left last time (just like any ebook reader would do). Here's how to +do that: + + (if (version< emacs-version "25.0") + (progn + (require 'saveplace) + (setq-default save-place t)) + (save-place-mode 1)) + + +The name novel mode is not my idea, there's a function on Xah's +ergomacs page with a function for this kind of stuff: +http://ergoemacs.org/emacs/emacs_novel_reading_mode.html. In fact, +this mode is based on this function, I had it in my .emacs file and +enhanced it all the time. At some point it made more sence to +maintain this baby in its own mode - hence novel-mode. + +# Install + +To use, save novel-mode.el to a directory in your load-path. + +Add something like this to your config: + + (require 'novel-mode) + (add-hook 'text-mode-hook 'novel-mode) + +or load it manually, when needed: + + M-x novel-mode + +# Customize + +You can customize the following variables: + +To setup a default left and right margin, use this: + + (setq novel-default-margin 50) + +All available novel-mode variables can be modified interactively +with: + + M-x customize-group RET novel-mode RET + +You can also use hooks to novel mode as a way to modify or enhance +its behavior. The following hooks are available: + + novel-mode-pre-start-hook + novel-mode-post-start-hook + novel-mode-pre-stop-hook + novel-mode-post-stop-hook + +Example: + + (add-hook 'novel-mode-post-start-hook + (lambda () + (set-face-font 'default "DejaVu Sans"))) + (add-hook 'novel-mode-post-stop-hook + (lambda () + (set-face-font 'default "Courier"))) + +# Meta + + +Copyright (C) 2016, T.v.Dein + +This file is NOT part of Emacs. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +Version: 0.01 +Author: T.v.Dein +Keywords: read books novels +URL: https://github.com/tlinden/novel-mode +License: GNU General Public License >= 2 diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..285a6b6 Binary files /dev/null and b/demo.gif differ diff --git a/novel-mode.el b/novel-mode.el new file mode 100644 index 0000000..1eafda1 --- /dev/null +++ b/novel-mode.el @@ -0,0 +1,423 @@ +;;; novel-mode.el --- screen reader + +;; Copyright (C) 2016, T.v.Dein + +;; This file is NOT part of Emacs. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of the +;; License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +;; USA + +;; Version: 0.01 +;; Author: T.v.Dein +;; Keywords: read books novels +;; URL: https://github.com/tlinden/novel-mode +;; License: GNU General Public License >= 2 + +;;; Commentary: + +;; Novel mode is a minor mode which converts emacs into a screen +;; reader, or in other words, it enables distraction free reading. It +;; is however not suited for distraction free editing. Try writeroom +;; mode if you're looking for this. + +;; When turned on, it does the following conversions: + +;; - disable almost all distractions, as menu, toolbar, scrollbar +;; - enlarge font size +;; - switch to variable width font +;; - enable word wrap (without fringe marker) +;; - increate line spacing +;; - adds a window margin to the left and right (thereby centering the text) +;; - disable all input keys (rendering the buffer read-only) +;; - disable the cursor +;; - switch to buffer-scrolling (like e.g. in Acroread) +;; - display current reading position in percent +;; - add a couple of convenience one-key commands + +;; Novel mode provides the following one-key commands, when active: + +;; n scroll one page down +;; p scroll one page up +;; scroll one line down +;; scroll one line up +;; mousewheel scroll linewise up or down +;; SPC scroll one page down +;; increase margins, makes visible text narrower +;; decrease margins, makes visible text wider +;; + increase font size +;; - decrease font size +;; i invert video display +;; q quit novel mode +;; ? display key mapping + +;; Important: while normal key input (beside the ones listed above), +;; is disabled, Control and Meta still work, of course. Please be also +;; aware that this mode might conflict with god-mode or evil-mode. + +;; If you use this mode quite often, then it might be a good idea to +;; use save-place mode, so that a text file will be opened where you +;; left last time (just like any ebook reader would do. Here's how to +;; do that: + +;; (if (version< emacs-version "25.0") +;; (progn +;; (require 'saveplace) +;; (setq-default save-place t)) +;; (save-place-mode 1)) + + +;; The name novel mode is not my idea, there's a function on Xah's +;; ergomacs page with a function for this kind of stuff: +;; http://ergoemacs.org/emacs/emacs_novel_reading_mode.html. In fact, +;; this mode is based on this function, I had it in my .emacs file and +;; enhanced it all the time. At some point it made more sence to +;; maintain this baby in its own mode - hence novel-mode. + +;;; Install: + +;; To use, save novel-mode.el to a directory in your load-path. + +;; Add something like this to your config: + +;; (require 'novel-mode) +;; (add-hook 'text-mode-hook 'novel-mode) + +;; or load it manually, when needed: + +;; M-x novel-mode + +;;; Customize: + +;; You can customize the following variables: + +;; To setup a default left and right margin, use this: + +;; (setq novel-default-margin 50) + +;; All available novel-mode variables can be modified interactively +;; with: + +;; M-x customize-group RET novel-mode RET + +;; You can also use hooks to novel mode as a way to modify or enhance +;; its behavior. The following hooks are available: + +;; novel-mode-pre-start-hook +;; novel-mode-post-start-hook +;; novel-mode-pre-stop-hook +;; novel-mode-post-stop-hook + +;; Example: + +;; (add-hook 'novel-mode-post-start-hook +;; (lambda () +;; (set-face-font 'default "DejaVu Sans"))) +;; (add-hook 'novel-mode-post-stop-hook +;; (lambda () +;; (set-face-font 'default "Courier"))) + + + + +;;; Code: +;;;; Constants: + +(defconst novel-mode-versioni "0.01" "Novel mode version") + +(defgroup novel-mode nil + "screen reader mode" + :group 'extensions + :group 'tools + :link '(url-link :tag "Repository" "https://github.com/tlinden/novel-mode")) + +;; various vars to remember previous states +(defconst novel--mlf mode-line-format) +(defconst novel--vlm visual-line-mode) +(defconst novel--ww word-wrap) +(defconst novel--mbm menu-bar-mode) +(defconst novel--tbm tool-bar-mode) +(defconst novel--sbm scroll-bar-mode) + +;; set on startup +(defvar novel--max-margin nil) +(defvar novel--cur-margin nil) + +;; remember last invertion state, if any. we start with t by purpose, keep this +(defvar novel--invert-state t) + + +;;;; Customizable variables: + +(defcustom novel-default-margin nil + "Initial margin (used left+right) in chars, calculated if nil.") + +(defcustom novel-feedback nil + "Display feedback in minibuffer on actions") + + +;;;; Internal Functions: + +(defun novel--cursor-pos() + "return current cursor position in %" + ;; percent code stolen shamelessly from simple.el/what-cursor-position + (interactive) + (let* ((total (buffer-size)) + (pos (point)) + (percent (if (> total 50000) + ;; Avoid overflow from multiplying by 100! + (/ (+ (/ total 200) (1- pos)) (max (/ total 100) 1)) + (/ (+ (/ total 2) (* 100 (1- pos))) (max total 1))))) + (message "%S%%" percent))) + +(defun novel--set-margins() + "Set window margins" + (interactive) + (set-window-margins nil novel--cur-margin novel--cur-margin) + (when novel-feedback + (message "set window margins to: %S (max: %S)" + novel--cur-margin novel--max-margin))) + + +;; disable self-insert-command. +;; remap code by Thorsten Jolitz, outshine.el, GPL2.0+ +(defun novel--self-insert-command(N) + (interactive "c") + (message "try ?")) + +(defun novel--remap-self-insert() + (novel--remap novel-mode-map 'self-insert-command 'novel--self-insert-command)) + +(defun novel--reset-remap-self-insert() + (novel--remap novel-mode-map 'novel--self-insert-command 'self-insert-command)) + +(defun novel--defkey (keymap key def) + "Define a KEY in a KEYMAP with definition DEF." + (define-key keymap key def)) + +(defun novel--remap (map &rest commands) + "In MAP, remap the functions given in COMMANDS. +COMMANDS is a list of alternating OLDDEF NEWDEF command names." + (let (new old) + (while commands + (setq old (pop commands) new (pop commands)) + (if (fboundp 'command-remapping) + (novel--defkey map (vector 'remap old) new) + (substitute-key-definition old new map global-map))))) + +;;;; Hooks: + +(defvar novel-mode-pre-start-hook () + "Called before startup") + +(defvar novel-mode-post-start-hook () + "Called after startup") + +(defvar novel-mode-pre-stop-hook () + "Called before stopping") + +(defvar novel-mode-post-stop-hook () + "Called after stopping") + +;;;; API Functions: + +(defun novel-up() + "Scroll buffer one line up" + (interactive) + (scroll-down 1) + (novel--cursor-pos)) + +(defun novel-down() + "Scroll buffer one line down" + (interactive) + (scroll-up 1) + (novel--cursor-pos)) + +(defun novel-page-up() + "Scroll buffer one page up" + (interactive) + (scroll-down) + (novel--cursor-pos)) + +(defun novel-page-down() + "Scroll buffer one page down" + (interactive) + (scroll-up) + (novel--cursor-pos)) + +;; invert everything, reverse it when called again +(defun novel-invert() + "Invert foreground and background colors" + (interactive) + (invert-face 'default) + (set-face-attribute 'fringe nil :inverse-video novel--invert-state) + (setq novel--invert-state (not novel--invert-state)) ;; cycle + (when novel-feedback + (message "inverted colors"))) + +(defun novel-incr-margins() + "Increment window margins" + (interactive) + (setq novel--cur-margin (if (= novel--cur-margin novel--max-margin) + novel--cur-margin + (+ novel--cur-margin 1))) + (novel--set-margins)) + +(defun novel-decr-margins() + "Decrement window margins" + (interactive) + (setq novel--cur-margin (if (= novel--cur-margin 0) 0 (- novel--cur-margin 1))) + (novel--set-margins)) + +(defun novel-incr-font-size () + "Increase font size" + (interactive) + (text-scale-increase 0.5) + (when novel-feedback + (message "increased font size"))) + +(defun novel-decr-font-size () + "Decrease font size" + (interactive) + (text-scale-increase -0.5) + (when novel-feedback + (message "decreased font size"))) + +(defun novel-toggle() + "Toggle reading mode" + (interactive) + (if (null (get this-command 'state-on-p)) + ;; enable, primary novel mode setup + (progn + (run-hooks 'novel-mode-pre-start-hook) + + (setq novel--max-margin (/ (- (window-body-width) 40) 2)) + (if (not novel-default-margin) + (setq novel-default-margin (/ (- (window-body-width) fill-column) 3))) + (setq novel--cur-margin novel-default-margin) + + (setq scroll-step 1 ; scroll linewise + scroll-conservatively 10000 + cursor-type nil ; no cursor + line-spacing 3 ; more distance between lines + mode-line-format nil ; no modeline + visual-line-mode t ; no wrap marker on fringe + word-wrap t + ) + + (delete-other-windows) ; There can be only one, McLeod. + (novel--set-margins) + + (variable-pitch-mode 1) ; enable variable width font + (text-scale-increase 2) ; larger font size + (put this-command 'state-on-p t) ; remeber current state + + (menu-bar-mode -1) ; disable widgets + (tool-bar-mode -1) + (scroll-bar-mode 0) + (set-fringe-mode 0) ; no fringe + + (novel--remap-self-insert) ; disable all keys but ours + + (run-hooks 'novel-mode-post-start-hook) + ) + + ;; disable, restore everything back to normal + (progn + (run-hooks 'novel-mode-pre-stop-hook) + + (setq scroll-step 0 + scroll-conservatively 0 + line-spacing nil + cursor-type 'box + mode-line-format novel--mlf + visual-line-mode novel--vlm + word-wrap novel--ww + ) + + (set-window-margins nil 0 0) + (variable-pitch-mode 0) + (text-scale-increase -2) + + (put this-command 'state-on-p nil) + + (menu-bar-mode novel--mbm) + (tool-bar-mode novel--tbm) + (scroll-bar-mode novel--sbm) + (set-fringe-mode 1) + + (novel--reset-remap-self-insert) + + (if (not novel--invert-state) + (novel-invert)) + + (run-hooks 'novel-mode-post-stop-hook) + )) + (redraw-frame (selected-frame))) + +(defun novel-help() + "Display help" + (interactive) + (message "Available commands in novel mode: + n scroll one page down + p scroll one page up + scroll one line down + scroll one line up + mousewheel scroll linewise up or down + SPC scroll one page down + increase margins, makes visible text narrower + decrease margins, makes visible text wider + + increase font size + - decrease font size + i invert video display + q quit novel mode + ? or h display key mapping +")) + +;;;; Interface + +;;;###autoload +(define-minor-mode novel-mode "screen reader mode" + :lighter " V" + :group 'novel-mode + :keymap (let ((map (make-sparse-keymap))) + (define-key map (kbd "") 'novel-up) + (define-key map (kbd "") 'novel-down) + (define-key map (kbd "") 'novel-up) + (define-key map (kbd "") 'novel-down) + + (define-key map (kbd "SPC") 'novel-page-down) + (define-key map (kbd "") 'novel-page-down) + (define-key map (kbd "") 'novel-page-up) + (define-key map (kbd "n") 'novel-page-down) + (define-key map (kbd "p") 'novel-page-up) + + (define-key map (kbd "") 'novel-decr-margins) + (define-key map (kbd "") 'novel-incr-margins) + + (define-key map (kbd "i") 'novel-invert) + (define-key map (kbd "+") 'novel-incr-font-size) + (define-key map (kbd "-") 'novel-decr-font-size) + (define-key map (kbd "q") 'novel-mode) + + (define-key map (kbd "h") 'novel-help) + (define-key map (kbd "?") 'novel-help) + map) + (novel-toggle) + ) + + +(provide 'novel-mode) + +;;; novel-mode.el ends here