Rework emojis in Org LaTeX export

This commit is contained in:
TEC 2022-12-04 19:34:53 +08:00
parent b3904e3201
commit c083c2475e
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
1 changed files with 197 additions and 72 deletions

View File

@ -11649,6 +11649,8 @@ Now all that remains is to hook this into the preamble generation.
**** Emojis
#+call: confpkg("ox-latex-emoji", after="ox-latex", prefix="")
It would be nice to actually include emojis where used.
Thanks to =emojify=, we have a folder of emoji images just sitting and waiting to
be used 🙂.
@ -11658,7 +11660,7 @@ constructing a regex for this would be a huge pain with the way the codepoints
are scattered around, but thanks to ~char-script-table~ we don't have to!
#+begin_src emacs-lisp
(defvar +emoji-rx
(defvar org-latex-emoji--rx
(let (emojis)
(map-char-table
(lambda (char set)
@ -11689,10 +11691,10 @@ surrogate form required by =\BeginAccSupp=.
\usepackage{transparent}
\newsavebox\emojibox
\NewDocumentCommand\DeclareEmoji{m m}{%
\NewDocumentCommand\DeclareEmoji{m m}{% UTF-8 codepoint, UTF-16 codepoint
\DeclareUnicodeCharacter{#1}{%
\sbox\emojibox{\raisebox{-0.3ex}{%
\includegraphics[height=1.8ex]{EMOJI-FOLDER/#1}}}%
\sbox\emojibox{\raisebox{OFFSET}{%
\includegraphics[height=HEIGHT]{EMOJI-FOLDER/#1}}}%
\usebox\emojibox
\llap{%
\resizebox{\wd\emojibox}{\height}{%
@ -11705,14 +11707,45 @@ Once we know that there are emojis present we can add a bit of preamble to the
buffer to make insertion easier.
#+begin_src emacs-lisp :noweb no-export :noweb-prefix no
(defconst org-latex-emoji-dir
(expand-file-name "emojis/twemoji-v2/" doom-cache-dir)
(defconst org-latex-emoji-base-dir
(expand-file-name "emojis/" doom-cache-dir)
"Directory where emojis should be saved and look for.")
(defvar org-latex-emoji-sets
'(("twemoji" :url "https://github.com/twitter/twemoji/archive/refs/tags/v14.0.2.zip"
:folder "twemoji-14.0.2/assets/svg" :height "1.8ex" :offset "-0.3ex")
("twemoji-bw" :url "https://github.com/youdly/twemoji-color-font/archive/refs/heads/v11-release.zip"
:folder "twemoji-color-font-11-release/assets/builds/svg-bw" :height "1.8ex" :offset "-0.3ex")
("openmoji" :url "https://github.com/hfg-gmuend/openmoji/releases/latest/download/openmoji-svg-color.zip"
:height "2.2ex" :offset "-0.45ex")
("openmoji-bw" :url "https://github.com/hfg-gmuend/openmoji/releases/latest/download/openmoji-svg-black.zip"
:height "2.2ex" :offset "-0.45ex")
("emojione" :url "https://github.com/joypixels/emojione/archive/refs/tags/v2.2.7.zip"
:folder "emojione-2.2.7/assets/svg") ; Warning, poor coverage
("noto" :url "https://github.com/googlefonts/noto-emoji/archive/refs/tags/v2.038.zip"
:folder "noto-emoji-2.038/svg" :file-regexp "^emoji_u\\([0-9a-f_]+\\)"
:height "2.0ex" :offset "-0.3ex"))
"An alist of plistst of emoji sets.
Specified with the minimal form:
(\"SET-NAME\" :url \"URL\")
The following optional parameters are supported:
:folder (defaults to \"\")
The folder within the archive where the emojis exist.
:file-regexp (defaults to nil)
Pattern with the emoji code point as the first capture group.
:height (defaults to \"1.8ex\")
Height of the emojis to be used.
:offset (defaults to \"-0.3ex\")
Baseline offset of the emojis.")
(defconst org-latex-emoji-keyword
"LATEX_EMOJI_SET"
"Keyword used to set the emoji set from `org-latex-emoji-sets'.")
(defvar org-latex-emoji-preamble <<grab("latex-emoji-preamble")>>
"LaTeX preamble snippet that will allow for emojis to be declared.
Containes the string \"EMOJI-FOLDER\" which should be replaced with
the value of `org-latex-emoji-dir'.")
Contains the string \"EMOJI-FOLDER\" which should be replaced with
the path to the emoji folder.")
(defun org-latex-emoji-utf16 (char)
"Return the pair of UTF-16 surrogates that represent CHAR."
@ -11721,6 +11754,7 @@ the value of `org-latex-emoji-dir'.")
(+ #xDC00 (logand char #x03FF))))
(defun org-latex-emoji-declaration (char)
"Construct the LaTeX command declaring CHAR as an emoji."
(format "\\DeclareEmoji{%X}{%s} %% %s"
char
(if (< char #xFFFF)
@ -11728,35 +11762,88 @@ the value of `org-latex-emoji-dir'.")
(apply #'format "%X%X" (org-latex-emoji-utf16 char)))
(capitalize (get-char-code-property char 'name))))
(defun org-latex-emoji-setup (&optional _info)
(concat
(replace-regexp-in-string
"EMOJI-FOLDER"
(directory-file-name
(if (getenv "HOME")
(replace-regexp-in-string
(regexp-quote (getenv "HOME"))
"\\string~"
org-latex-emoji-dir t t)
org-latex-emoji-dir))
org-latex-emoji-preamble t t)
"\n\n"
(mapconcat
#'org-latex-emoji-declaration
(let (unicode-cars)
(save-excursion
(goto-char (point-min))
(while (re-search-forward +emoji-rx nil t)
(push (aref (match-string 0) 0) unicode-cars)))
(cl-delete-duplicates unicode-cars))
"\n")
"\n"))
(defun org-latex-emoji-fill-preamble (emoji-folder &optional height offset svg-p)
"Fill in `org-latex-emoji-preamble' with EMOJI-FOLDER, HEIGHT, and OFFSET.
If SVG-P is set \"includegraphics\" will be replaced with \"includesvg\"."
(let* (case-fold-search
(filled-preamble
(replace-regexp-in-string
"HEIGHT"
(or height "1.8ex")
(replace-regexp-in-string
"OFFSET"
(or offset "-0.3ex")
(replace-regexp-in-string
"EMOJI-FOLDER"
(directory-file-name
(if (getenv "HOME")
(replace-regexp-in-string
(regexp-quote (getenv "HOME"))
"\\string~"
emoji-folder t t)
emoji-folder))
org-latex-emoji-preamble t t)
t t)
t t)))
(if svg-p
(replace-regexp-in-string
"includegraphics" "includesvg"
filled-preamble t t)
filled-preamble)))
(defun org-latex-emoji-setup (&optional info)
"Construct a preamble snippet to set up emojis based on INFO."
(let* ((emoji-set
(or (org-element-map
(plist-get info :parse-tree)
'keyword
(lambda (keyword)
(and (string= (org-element-property :key keyword)
org-latex-emoji-keyword)
(org-element-property :value keyword)))
info t)
(caar org-latex-emoji-sets)))
(emoji-spec (cdr (assoc emoji-set org-latex-emoji-sets)))
(emoji-folder
(expand-file-name emoji-set org-latex-emoji-base-dir))
(emoji-svg-only
(and (file-exists-p emoji-folder)
(not (cl-some
(lambda (path)
(not (string= (file-name-extension path) "svg")))
(directory-files emoji-folder nil "\\....$"))))))
(cond
((not emoji-spec)
(error "Emoji set `%s' is unknown. Try one of: %s" emoji-set
(string-join (mapcar #'car org-latex-emoji-sets) ", ")))
((not (file-exists-p emoji-folder))
(if (and (not noninteractive)
(yes-or-no-p (format "Emoji set `%s' is not installed, would you like to install it?" emoji-set)))
(org-latex-emoji-install
emoji-set
(or (executable-find "cairosvg") (executable-find "inkscape")))
(error "Emoji set `%s' is not installed" emoji-set))))
(concat
(org-latex-emoji-fill-preamble
emoji-folder (plist-get emoji-spec :height)
(plist-get emoji-spec :offset) emoji-svg-only)
"\n\n"
(mapconcat
#'org-latex-emoji-declaration
(let (unicode-cars)
(save-excursion
(goto-char (point-min))
(while (re-search-forward org-latex-emoji--rx nil t)
(push (aref (match-string 0) 0) unicode-cars)))
(cl-delete-duplicates unicode-cars))
"\n")
"\n")))
(add-to-list 'org-export-conditional-features
(cons (lambda (_info)
(save-excursion
(goto-char (point-min))
(re-search-forward +emoji-rx nil t)))
(re-search-forward org-latex-emoji--rx nil t)))
'emoji)
t)
@ -11769,9 +11856,8 @@ perform. =emojify= downloads the ~72x72~ versions of Twemoji, however SVG versio
are also produced. We could use ~inkscape~ to convert those to PDFs, which would
likely be best for including.
First, it's worth checking whether =.pdf= graphics files will be prioritised over
=.png= files. If so, that would be ideal as no extra effort is required past
fetching and converting the files.
This works fairly nicely, but it would be good to use =.pdf= forms whenever
possible. We can use =texdef= to check the file extension priority list.
#+begin_src shell :tangle no :exports both :results output verbatim :wrap example
texdef -t pdflatex -p graphicx Gin@extensions
@ -11783,34 +11869,82 @@ texdef -t pdflatex -p graphicx Gin@extensions
macro:->.pdf,.png,.jpg,.mps,.jpeg,.jbig2,.jb2,.PDF,.PNG,.JPG,.JPEG,.JBIG2,.JB2,.eps
#+end_example
Fantastic! We can see that =.pdf= actually comes first in the priority list. So
now we just need to fetch and convert those SVGs --- ideally with a handy
command to do so for us.
Fantastic! We can see that =.pdf= actually comes first in the priority list.
Now we just need to fetch and convert the emoji images.
#+begin_src emacs-lisp
(defun org-latex-emoji-install-vector-graphics ()
"Dowload, convert, and install vector emojis for use with LaTeX."
(interactive)
(let ((dir (org-latex-emoji-install-vector-graphics--download)))
(org-latex-emoji-install-vector-graphics--convert dir)
(org-latex-emoji-install-vector-graphics--install dir))
(message "Vector emojis installed."))
(defun org-latex-emoji-install (set &optional convert)
"Dowload, convert, and install emojis for use with LaTeX."
(interactive
(list (completing-read "Emoji set to install: "
(mapcar
(lambda (set-spec)
(if (file-exists-p (expand-file-name (car set-spec) org-latex-emoji-base-dir))
(propertize (car set-spec) 'face 'font-lock-doc-face)
(car set-spec)))
org-latex-emoji-sets)
nil t)
(if (or (executable-find "cairosvg") (executable-find "inkscape"))
(yes-or-no-p "Would you like to create .pdf forms of the Emojis (strongly recommended)?")
(message "Install `cairosvg' (recommended) or `inkscape' to convert to PDF forms")
nil)))
(let ((emoji-folder (expand-file-name set org-latex-emoji-base-dir)))
(when (or (not (file-exists-p emoji-folder))
(and (not noninteractive)
(yes-or-no-p "Emoji folder already present, would you like to re-download?")
(progn (delete-directory emoji-folder) t)))
(let* ((spec (cdr (assoc set org-latex-emoji-sets)))
(dir (org-latex-emoji-install--download set (plist-get spec :url)))
(svg-dir (expand-file-name (or (plist-get spec :folder) "") dir)))
(org-latex-emoji-install--install
set svg-dir (plist-get spec :file-regexp))))
(when convert
(org-latex-emoji-install--convert (file-name-as-directory emoji-folder))))
(message "Emojis set `%s' installed." set))
(defconst org-latex-emoji-source-url
"https://github.com/twitter/twemoji/archive/refs/tags/v14.0.2.zip"
"URL to the (tw)emoji source archive.")
(defun org-latex-emoji-install-vector-graphics--download ()
(let* ((twemoji-version (replace-regexp-in-string "^.*tags/v\\(.*\\)\\.zip" "\\1" org-latex-emoji-source-url))
(twemoji-dest-folder (make-temp-file "twemoji-" t)))
(message "Downloading Twemoji v%s" twemoji-version)
(let ((default-directory twemoji-dest-folder))
(call-process "curl" nil nil nil "-L" org-latex-emoji-source-url "--output" "twemoji.zip")
(defun org-latex-emoji-install--download (name url)
"Download the emoji archive URL for the set NAME."
(let* ((dest-folder (make-temp-file (format "%s-" name) t)))
(message "Downloading %s..." name)
(let ((default-directory dest-folder))
(call-process "curl" nil nil nil "-sL" url "--output" "emojis.zip")
(message "Unzipping")
(call-process "unzip" nil nil nil "twemoji.zip")
(concat twemoji-dest-folder "/twemoji-" twemoji-version "/assets/svg"))))
(call-process "unzip" nil nil nil "emojis.zip")
dest-folder)))
(defun org-latex-emoji-install-vector-graphics--convert (dir)
(defun org-latex-emoji-install--install (name dir &optional filename-regexp)
"Install the emoji files in DIR to the NAME set folder.
If a FILENAME-REGEXP, only files matching this regexp will be moved,
and they will be renamed to the first capture group of the regexp."
(message "Installing %s emojis into emoji directory" name)
(let ((images (append (directory-files dir t ".*.svg")
(directory-files dir t ".*.pdf")))
(emoji-dir (file-name-as-directory
(expand-file-name name org-latex-emoji-base-dir))))
(unless (file-exists-p emoji-dir)
(make-directory emoji-dir t))
(mapc
(lambda (image)
(if filename-regexp
(when (string-match filename-regexp (file-name-nondirectory image))
(rename-file image
(expand-file-name
(file-name-with-extension
(downcase (match-string 1 (file-name-nondirectory image)))
(file-name-extension image))
emoji-dir)
t))
(rename-file image
(expand-file-name
(downcase (file-name-nondirectory image))
emoji-dir)
t)))
images)
(message "%d emojis installed" (length images))))
(defun org-latex-emoji-install--convert (dir)
"Convert all .svg files in DIR to .pdf forms.
Uses cairosvg if possible, falling back to inkscape."
(let ((default-directory dir))
(if (executable-find "cairosvg") ; cairo's PDFs are ~10% smaller
(let* ((images (directory-files dir nil ".*.svg"))
@ -11820,7 +11954,8 @@ command to do so for us.
(threads 0))
(while (< index num-images)
(setf threads (1+ threads))
(message "Converting emoji %d/%d (%s)" (1+ index) num-images (nth index images))
(let (message-log-max)
(message "Converting emoji %d/%d (%s)" (1+ index) num-images (nth index images)))
(make-process :name "cairosvg"
:command (list "cairosvg" (nth index images) "-o" (concat (file-name-sans-extension (nth index images)) ".pdf"))
:sentinel (lambda (proc msg)
@ -11834,17 +11969,6 @@ command to do so for us.
(message "Cairosvg not found. Proceeding with inkscape as a fallback.")
(shell-command "inkscape --batch-process --export-type='pdf' *.svg"))
(message "Finished conversion!")))
(defun org-latex-emoji-install-vector-graphics--install (dir)
(message "Installing vector emojis into emoji directory")
(let ((images (directory-files dir t ".*.pdf"))
(emoji-dir (file-name-as-directory org-latex-emoji-dir)))
(unless (file-exists-p emoji-dir)
(make-directory emoji-dir t))
(mapc
(lambda (image)
(rename-file image emoji-dir t))
images)))
#+end_src
**** Remove non-ascii chars
@ -12125,8 +12249,9 @@ export to a PDF.
#+name: org-missing-latex-packages
#+begin_src emacs-lisp :noweb-ref none :var org-latex-required-packages-list=org-latex-required-packages-list[,0] :var org-latex-font-packages-list=org-latex-font-packages-list
(setq org-required-latex-packages (append org-latex-required-packages-list
(mapcar #'car org-latex-font-packages-list)))
(setq org-required-latex-packages
(append org-latex-required-packages-list
org-latex-font-packages-list))
(defun check-for-latex-packages (packages)
(delq nil (mapcar (lambda (package)