User:Riviera/Emacs layout

From XPUB & Lens-Based wiki
< User:Riviera
Revision as of 22:47, 23 May 2024 by Riviera (talk | contribs) (added sections and wrote about paragraphs which end with one word on the final line)

Part One

An aim of the reading, writing and research methods seminars this trimester is to create a personal reader. In a previous wiki post, I discussed my engagement with free software based solutions to the typographical challenges posed by this task. This wiki post picks up on the same theme from a perspective closer to these wiki posts about Emacs. The outcome is a result of minimally typesetting, and then printing Emacs buffers. The font is Deja Vu Mono, the text has double height line spacing and it’s paginated more or less by hand.

To facilitate with this endeavor, I wrote a handful of functions in Emacs Lisp.

Right Margin

Below is an example:

(defun configure-right-margin (right-margin-width file 
                    &optional fill-column justify)
  "Configure RIGHT-MARGIN-WIDTH in FILE, optionally set FILL-COLUMN or JUSTIFY"
  (find-file-other-window file)
  (progn
    (set-right-margin (point-min) (point-max) right-margin-width)
    (if fill-column
    (set-fill-column fill-column)
      nil)
    (if justify
    (fill-region (point-min) (point-max) justify)
       nil)))

The function weaves together various actions which I would need to perform by hand in Emacs for each text. The idea is that one could call this script on a file whilst running Emacs in Batch Mode. I hope this could be achieved by running a command such as:

$ emacs -Q --batch -l paginate.el --eval '(configure-right-margin 8 "foo.txt")'

This command runs Emacs non-interactively. It loads the file with the function definitions into memory and then evaluates one of those defined functions. Presumably there is a way in which this could be hooked into a python or Bash script.


Pagination

Here’s another function which I wrote:

(defun paginate (page-length initial-page-number offset)
  (progn
    (insert-char 12) ; form feed
    (insert-char 13 2) ; carriage return twice
    (newline)
    ;; format page number
    (insert-char 32 (- fill-column offset)) ; space
    (insert "[" (int-to-string initial-page-number) "]") ; pg number in header
    (setq initial-page-number (1+ initial-page-number))
    (newline)
    (insert-char 13)
    (open-line 1)
    (forward-line (- page-length 4))
    (forward-line)
    (if (= (point) (point-max))
    (beginning-of-buffer)
      (list (forward-line (- 0 1))
        (paginate page-length initial-page-number offset)))))

The script inserts particular characters at a given point on occasions which are determined by variables such as page-length. Functions in lines 12-17 illustrate the spatial aspects of Emacs. Furthermore, it is a recursive function which, in this case, means it calls itself until completion.


Part Two

Runts

In typography a paragraph that contains a single word in its final line is known as a runt (according to Wikipedia). The presence of runts in typeset text is considered poor typography. As part of my attempts to use Emacs as a layout engine I wrote a script which removes runts from buffers. Here’s how I did so.

Firstly, a function needs to be defined which checks if the current line contains a single word followed by ‘.’. That is handled by the following function.

(defun current-line-is-runt-p ()
    "Return non-nil if the current line is a runt."
  (save-excursion
    (beginning-of-line)
    (looking-at-p "^[[:word:]]+\\.$")))

The next function, correct-runts, is based on a while loop. Initially, on line 7, the code moves point to what Emacs regards as the end of the paragraph. It then checks, via current-line-is-runt-p, whether the current line is a runt. If so, it moves point back to the beginning of the buffer, increments the column width by one character and finally applies justification. At that point the while loop kicks in and the process continues. The condition for the while loop can be interpreted as “exit on success or failure”. Success is defined in terms of point reaching the end of the buffer. Failure, on the other hand. occurs when the values of fill-column-min and fill-column-max are equal. By implication, success amounts to removing all runts from the document within the specified range of column widths.

(defun correct-runts (fill-column-min fill-column-max right-margin-width
                      justify)
  "Check every paragraph for runts. Attempt to remove runts by adjusting
the value of fill-column within the range of FILL-COLUMN-MIN and
FILL-COLUMN-MAX."
  (while (not (or (= (point) (point-max)) (= fill-column-min fill-column-max)))
    (end-of-paragraph-text)
    (if (current-line-is-runt-p)
    (list (beginning-of-buffer)
          (setq fill-column-min (1+ fill-column-min))
          (set-fill-column fill-column-min)
          (set-right-margin (point-min) (point-max) right-margin-width)
          (fill-region (point-min) (point-max) justify))
      )))

Finally, I wrote an additional function which allows me to execute the programme on text in a particular file.

(defun correct-runts-in-file (file fill-column-min fill-column-max
                   right-margin-width justify)
  "Execute `correct-runts' on FILE"
  (find-file-other-window file)
  (correct-runts fill-column-min fill-column-max right-margin-width
         justify))