I made an Emacs minor mode to transcribe sitelen pona
It is well known between me and my close friends that I really, really enjoy toki pona. The language comes with a logographic writing system called sitelen pona.
In order to type it, one can resort to fonts that respect UCSUR; UCSUR is a project whose goal is to coordinate the assignment of blocks out of the Unicode Private Use Area to constructed or artificial languages or scripts.
Here's the catch: sitelen pona is hard to input with a normal keyboard, without going through the hassle of creating a custom keyboard layout or typing the hex codes yourself... Hence why I decided to make a minor mode for Emacs!
Here I explain how I managed to achieve this
Creating the Emacs minor mode
I first started creating a minor mode through the define-minor-mode
macro, which also sets up every useful variable and hook for me already.
All this mode does is adding or removing a hook to the function toki-pona-auto-replace
; note that this is a buffer local minor mode - we don't want to translate every toki pona word everywhere by default.
(define-minor-mode toki-pona-input-mode
"Minor mode to auto-replace Toki Pona words with glyphs."
:lighter " toki"
:global nil
(if toki-pona-input-mode
(add-hook 'post-self-insert-hook #'toki-pona-auto-replace nil t)
(remove-hook 'post-self-insert-hook #'toki-pona-auto-replace t)))
The hook function and the replacing function
Next up, let's see what the toki-pona-auto-replace
function does:
If the latest input was a space, it runs the actual replacing function
(defun toki-pona-auto-replace ()
"Hook function for toki-pona-minor-mode"
(when (and toki-pona-input-mode
(eq last-command-event ?\s))
(toki-pona-replace-word-before-space)))
Now let's take a look at the heart of this minor mode, the function that replaces the characters:
(defun toki-pona-replace-word-before-space ()
"Replace Toki Pona word before point (if preceded by space) with glyph."
(when (eq (char-before) ?\s)
(let* ((space-pos (point))
(end (save-excursion (backward-word) (forward-word) (point)))
(start (save-excursion (backward-word) (point)))
(word (string-trim-right (buffer-substring-no-properties start end)))
(glyph (cdr (assoc word toki-pona-input-alist))))
(when glyph
(goto-char start)
(delete-region start space-pos)
(let ((after-pos (point)))
(insert-char glyph)
(goto-char (1+ after-pos)))))))
This function goes back to the beginning of the word, saves the start and end points, trims excessive spaces and then searches the alist called toki-pona-input-alist
; that list contains every word paired with its corresponding hex code for the glyph under UCSUR. The found glyph is saved into the glyph
variable.
If the search was successful, it proceeds to delete the corresponding word, insert the glyph, then go back to the end of the word.
The big alist
The list of words and hex codes is of this form
(defvar toki-pona-input-alist
'(("a" . #xF1900)
("akesi" . #xF1901)
("ala" . #xF1902)
("alasa" . #xF1903)
("ale" . #xF1904)
;; ... many more words
("pake" . #xF19A0)
("apeja" . #xF19A1)
("majuna" . #xF19A2)
("powe" . #xF19A3)))
It is manually hard-coded, since toki pona has about 130 words only1.
So... Does it work?
Well...
Yeah!
The only thing I still have no clue how to implement are cartouches for proper names and long line ligatures for words like pi (), tawa (), kepeken (), etc.
Now here's the funny font part
This mode, once (eventually) provide
d to Emacs, works wonders. The only issue I was encountering was that I had to always use Fairfax HD, which supports UCSUR, in order to display these characters.
This is not a big issue, until I decided that I want to sitelen pona in my Org files... my beloved Org Mode...
I didn't know at the time I was about to crash into my absolutely awful font configuration in Emacs, which came back to bite my bear butt many times.
This took me hours to figure out, but it is very much worth it in the end.
After my many troubles with the Emacs daemon, looking through the man pages and the web, fonts not loading correctly the first time, I ended up with the following working configuration in my init file:
(defun dusk/set-fonts (frame)
(with-selected-frame frame
(when (display-graphic-p frame)
(set-face-attribute 'default nil :family "Fairfax HD" :height 160 :weight 'normal)
(set-face-attribute 'fixed-pitch nil :family "Agave" :height 150 :weight 'normal)
(set-face-attribute 'variable-pitch nil :family "ETBembo" :height 180 :weight 'medium)
(set-fontset-font "fontset-default" '(#xF1900 . #xF19A3)
(font-spec :family "Fairfax HD") nil 'prepend))))
(when (display-graphic-p)
(dusk/set-fonts (selected-frame)))
(add-hook 'after-make-frame-functions #'dusk/set-fonts)
Of course, one can replace the other fonts with whatever they prefer2.
The important line here is the call to the function set-fontset-font
, which allows me to tell Emacs to use the font only for the range I want (in the case of toki pona, '(#xF1900 . #xF19A3)
)
Conclusion
Now I can finally type sitelen pona and enjoy my favorite minimalist constructed language in my favorite piece of software, Emacs.
Next time I'll try implementing proper names and the font ligatures.
See you next time!