;;; ox-latex-emoji.el --- Generated package (no.83) from my config -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2024 TEC
;;
;; Author: TEC <https://code.tecosaur.net/tec>
;; Maintainer: TEC <contact@tecosaur.net>
;; Created: April 20, 2024
;; Modified: April 20, 2024
;; Version: 2024.04.20
;; Homepage: https://code.tecosaur.net/tec/emacs-config
;; Package-Requires: ((emacs "29.1"))
;;
;; This file is not part of GNU Emacs.
;;
;;; Commentary:
;;
;;  Generated package (no.83) from my config.
;;
;;  During generation, dependency on other aspects of my configuration and
;;  packages is inferred via (regexp-based) static analysis.  While this seems
;;  to do a good job, this method is imperfect.  This code likely depends on
;;  utilities provided by Doom, and if you try to run it in isolation you may
;;  discover the code makes more assumptions.
;;
;;  That said, I've found pretty good results so far.
;;
;;; Code:

(require 'ox-latex)

(defvar org-latex-emoji--rx
  (let (emojis)
    (map-char-table
     (lambda (char set)
       (when (eq set 'emoji)
         (push (copy-tree char) emojis)))
     char-script-table)
    (rx-to-string `(any ,@emojis)))
  "A regexp to find all emoji-script characters.")

(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 "\\usepackage{accsupp}
% The transparent package is also needed, but will be loaded later.
\\newsavebox\\emojibox

\\NewDocumentCommand\\DeclareEmoji{m m}{% UTF-8 codepoint, UTF-16 codepoint
  \\DeclareUnicodeCharacter{#1}{%
    \\sbox\\emojibox{\\raisebox{OFFSET}{%
        \\includegraphics[height=HEIGHT]{EMOJI-FOLDER/#1}}}%
    \\usebox\\emojibox
    \\llap{%
      \\resizebox{\\wd\\emojibox}{\\height}{%
        \\BeginAccSupp{method=hex,unicode,ActualText=#2}%
        \\texttransparent{0}{X}%
        \\EndAccSupp{}}}}}"
  "LaTeX preamble snippet that will allow for emojis to be declared.
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."
  (list
   (+ #xD7C0 (ash char -10))
   (+ #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)
              (format "%X" char)
            (apply #'format "%X%X" (org-latex-emoji-utf16 char)))
          (capitalize (get-char-code-property char 'name))))

(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))))
    (org-latex-emoji-fill-preamble
     emoji-folder (plist-get emoji-spec :height)
     (plist-get emoji-spec :offset) emoji-svg-only)))

(org-export-update-features 'latex
  (emoji-setup ; The precompilable bit
   :condition (save-excursion
                (goto-char (point-min))
                (re-search-forward org-latex-emoji--rx nil t))
   :requires (image pkg-transparent)
   :snippet org-latex-emoji-setup
   :order 3)
  (pkg-transparent ; Part of emoji setup, but non-precompilable.
   :snippet "\\usepackage{transparent}"
   :order 84)
  (emoji-declarations
   :condition t
   :when emoji-setup
   :snippet
   (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")
   :order 85))

(org-export-update-features 'latex
  (emoji-lualatex-hack
   :condition t
   :when (emoji julia-code) ; LuaLaTeX is used with julia-code.
   :snippet
   "\\usepackage{newunicodechar}
\\newcommand{\\DeclareUnicodeCharacter}[2]{%
    \\begingroup\\lccode`|=\\string\"#1\\relax
    \\lowercase{\\endgroup\\newunicodechar{|}}{#2}}"
   :before emoji))

(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))

(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 "emojis.zip")
      dest-folder)))

(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
                            (upcase (match-string 1 (file-name-nondirectory image)))
                            (file-name-extension image))
                           emoji-dir)
                          t))
         (rename-file image
                      (expand-file-name
                       (file-name-with-extension
                        (upcase (file-name-nondirectory image))
                        (file-name-extension 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"))
               (num-images (length images))
               (index 0)
               (max-threads (1- (string-to-number (shell-command-to-string "nproc"))))
               (threads 0))
          (while (< index num-images)
            (setf threads (1+ threads))
            (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)
                                      (when (memq (process-status proc) '(exit signal))
                                        (setf threads (1- threads)))))
            (setq index (1+ index))
            (while (> threads max-threads)
              (sleep-for 0.01)))
          (while (> threads 0)
            (sleep-for 0.01)))
      (message "Cairosvg not found. Proceeding with inkscape as a fallback.")
      (shell-command "inkscape --batch-process --export-type='pdf' *.svg"))
    (message "Finished conversion!")))

(provide 'ox-latex-emoji)
;;; ox-latex-emoji.el ends here