diff --git a/config.org b/config.org index 9742f21..46c20ff 100644 --- a/config.org +++ b/config.org @@ -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 <> "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)