Hi

Let's go by larger chunks. Maybe this way it is less confusing. Attached is
a larger patch with all that is needed to support fontspec config. This is
basically for the fallback fonts only. No language support, which continues
to be with \usepackage[AUTO]{babel} or \usepackage[AUTO]{polyglossia}.
That will eventually come in the future... if this is finally accepted...

/PA
ox-latex/ox-beamer maintainer
-- 
Fragen sind nicht da, um beantwortet zu werden,
Fragen sind da um gestellt zu werden
Georg Kreisler

"Sagen's Paradeiser" (ORF: Als Radiohören gefährlich war) => write BE!
Year 2 of the New Koprocracy

This was produced by a human (implied virtues and weaknesses acknowledged)
I'd hate this being fed to any form of AS (sorry AI)...
From bf442a622c5b9acfb1a3c83a5ed5645c70faa91c Mon Sep 17 00:00:00 2001
From: "Pedro A. Aranda" <[email protected]>
Date: Tue, 30 Jun 2026 09:33:31 +0200
Subject: [PATCH] Introducing fontspec for ox-beamer and ox-latex

A single commit with all the changes related to fontspec font control.

* doc/org-manual.org: Document keywords BEAMER_THEME_PRE and
LATEX_FONTSPEC_FEATURES.  New section describing font control when the
fontspec package is used; includes a subsection for fallback fonts.
* etc/ORG-NEWS: Announce BEAMER_THEME_PRE, org-latex-fontspec-config,
org-latex-default-fontspec-features (and associated keyword
LATEX_FONTSPEC_DEFAULTS).  Describe font handling.  Acknowledge code
used in org-latex--get-doc-scripts
* lisp/org.el (org-latex-default-packages-alist): replace amsmath with
unicode-math.
* lisp/ox-beamer.el: add beamer-theme-pre to backend.
(org-beamer--create-theme): New function to generate theme related
information.
(org-beamer--insert-theme-config): New function to insert the theme info
right after the document class.
(org-beamer-template): apply the previous functions on
org-latex-make-preamble.
* lisp/ox-latex.el: backend: add latex-fontspec-defaults.
(org-latex-fontspec-config): new variable to store the fontspec
configuration.
(org-latex--get-doc-scripts): new function to detect scripts in current
buffer.
(org-latex--parse-font-misses): new function to warn user about missing
scripts.
(org-latex--fontspec-prelude): new function to generate the fontspec
fallbacks.
(org-latex-make-preamble): Add fontspec config to preamble.
(org-latex-compile--postprocess): Add font misses to user warnings.
* testing/lisp/test-ox-beamer.el (test-ox-beamer/fontspec): New test for
BEAER_THEME_PRE and fontspec configuration in Beamer.
* testing/lisp/test-ox-latex.el (test-ox-latex/lualatex-fontspec-plain):
Simple fontspec config without default font features.
(test-ox-latex/lualatex-fontspec-plain): Put font features inside the
fontspec config for a specfic fot family.
(test-ox-latex/lualatex-fontspec-plain): The fallback is handled
correctly.
(test-ox-latex/lualatex-fontspec-noemoji): No fallback is created if not
necessary.
(test-ox-latex/lualatex-fontspec-default-features): handle default font
features.
(test-ox-latex/lualatex-no-fontspec-default-features): No fontspec for
pdflatex.
---
 doc/org-manual.org             | 157 ++++++++++++++++++++++++++
 etc/ORG-NEWS                   |  41 +++++++
 lisp/org.el                    |  10 +-
 lisp/ox-beamer.el              |  62 ++++++----
 lisp/ox-latex.el               | 199 ++++++++++++++++++++++++++++++++-
 testing/lisp/test-ox-beamer.el |  30 +++++
 testing/lisp/test-ox-latex.el  | 131 ++++++++++++++++++++++
 7 files changed, 602 insertions(+), 28 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 9201a341a..f84cfc52d 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13173,6 +13173,18 @@ settings (see [[*Export Settings]]).
   #+cindex: @samp{BEAMER_OUTER_THEME}, keyword
   The Beamer outer theme.
 
+- =BEAMER_THEME_PRE= ::
+  Arbitrary lines inserted in the preamble, just before the Beamer
+  theme is defined with ~\usetheme{}~. This can used, for example, to
+  import the geometry package to fine-tune the presentation page
+  size:
+
+  #+BEGIN_SRC org
+,#+BEAMER_THEME_PRE: \usepackage{geometry}
+,#+BEAMER_THEME_PRE: \geometry{paperwidth=160mm, paperheight=90mm}
+,#+BEAMER_THEME: Boadilla
+  #+END_SRC
+
 - =BEAMER_HEADER= ::
 
   #+cindex: @samp{BEAMER_HEADER}, keyword
@@ -14310,6 +14322,19 @@ general options (see [[*Export Settings]]).
   ~org-latex-title-command~ for typesetting description into the
   document's front matter.
 
+- =LATEX_FONTSPEC_FEATURES= ::
+
+  #+cindex: @samp{LATEX_FONTSPEC_FEATURES}, keyword
+  #+vindex: org-latex-fontspec-default-features
+  The default features the ~fontspec~ package will apply to the
+  document's fonts. This keyword or
+  ~org-latex-fontspec-default-features~ should hold a string with a
+  comma-separated list of settings for ~fontspec~-related
+  features. Refer to the
+  [[https://texdoc.org/serve/fontspec/0][package's manual]] Part III,
+  Section 1 for more details.
+
+
 The following sections have further details.
 
 *** LaTeX header and sectioning structure
@@ -15084,6 +15109,138 @@ contents, unless you set the =UNNUMBERED= property to =notoc=:
 :END:
 #+end_example
 
+*** Controlling font setup with the ~fontspec~ package (for LuaLaTeX and XeLaTeX)
+:PROPERTIES:
+:CUSTOM_ID: fontspec-configuration
+:END:
+
+#+vindex: org-latex-fontspec-config
+Modern, UTF8-capable LaTeX compilers LuaLaTeX and XeLaTeX configure
+fonts using the ~fontspec~ package.  Org provides two variables to
+fine-tune font configuration with this package.
+
+LaTeX document classes include a set of default fonts, usually from
+the [[https://www.gust.org.pl/projects/e-foundry/latin-modern][Latin
+Modern (LM) Family of Fonts]].  If you need to use other fonts, you can
+do so by mapping your font preferences to the four main font families
+defined in LaTeX:
+
+| "main" | Roman or Serif font (default)     |
+| "sans" | Sans Serif font                   |
+| "mono" | Monospaced (terminal) font        |
+| "math" | Font for mathematical expressions |
+
+This mapping takes place in ~org-latex-fontspec-config~.  Although not
+mandatory, it is recommended that you define mapping for the four
+families.  An example mapping the fonts to use another popular font family,
+[[https://www.gust.org.pl/projects/e-foundry/tex-gyre][the TeX Gyre
+(TG) Collection of Fonts]] could be [fn::We use this family, because
+it is also included in most LaTeX distributions.]:
+
+#+BEGIN_SRC emacs-lisp
+(setq org-latex-fontspec-config
+      '(("main" :font "TeX Gyre Termes")
+        ("sans" :font "TeX Gyre Heros")
+        ("mono" :font "TeX Gyre Cursor")
+        ("math" :font "TeX Gyre Math"))
+#+END_SRC
+
+As you see, each element in the list maps one of the LaTeX font
+families to a property =:font=, which states the font installed in
+your system.
+
+Additionally, you can also define "features" for each of the fonts,
+which are additional settings to modify their appearance.  A common
+example is scaling the mono-typed font to approximately match the size
+of the ~main~ font and provide a more uniform appearance to your
+document:
+
+#+BEGIN_SRC emacs-lisp
+(setq org-latex-fontspec-config
+      '(("main" :font "TeX Gyre Termes")
+        ("sans" :font "TeX Gyre Heros")
+        ("mono" :font "TeX Gyre Cursor" :props "Scale=MatchLowercase")
+        ("math" :font "TeX Gyre Math"))
+#+END_SRC
+
+#+vindex: org-latex-fontspec-default-features
+Finally, you can also set common features for _all_ fonts with the
+variable ~org-latex-fontspec-default-features~ or in the document
+header:
+
+#+BEGIN_SRC emacs-lisp
+,#+LATEX_FONTSPEC_DEFAULTS: Scale=MatchLowercase
+#+END_SRC
+
+or
+
+#+BEGIN_SRC emacs-lisp
+(setq org-latex-fontspec-default-features "Scale=MatchLowercase")
+#+END_SRC
+
+**** Fallback fonts with LuaLaTeX
+:PROPERTIES:
+:UNNUMBERED: t
+:END:
+
+You are warned by Org that your document could not be correctly
+typeset because your selection of fonts doesn't cover all scripts used
+in the document:
+#+BEGIN_EXAMPLE
+⛔ Warning (ox-latex): PDF file produced with warnings: [Missing character(s): please load an appropriate font with the fontspec package]
+#+END_EXAMPLE
+
+In this event, Emacs will also provide you with a
+list of fonts and *Emacs* script families that caused problems:
+
+#+BEGIN_EXAMPLE
+⛔ Warning (emacs): Missing scripts:
+For font TeXGyreHeros: emoji
+#+END_EXAMPLE
+
+Although modern Unicode-compatible fonts can represent a wide range of
+characters or glyphs, it is virtually impossible to cover all possible
+glyphs in one font.  When you use LuaLaTeX, you can replace glyphs
+that are not provided by your fonts on the fly using a feature called
+fallback fonts.
+
+In this case, *and only if you are using LuaLaTeX*, you will be able
+to use other fonts you have installed in your system to provide the
+missing glyphs.  An example is a document using emojis.  In this case,
+provided you have the =Noto Color Emoji= font installed in your
+system, you could use it to add the missing glyphs, using the
+=:fallback= property for the reported font:
+
+#+BEGIN_SRC emacs-lisp
+(setq org-latex-fontspec-config
+      '(("main" :font "TeX Gyre Termes"))
+        ("sans" :font "TeX Gyre Heros"
+         :fallback (("emoji" . "Noto Color Emoji:mode=harf")))
+        ("mono" :font "TeX Gyre Cursor" :props "Scale=MatchLowercase")
+        ("math" :font "TeX Gyre Math"))
+#+END_SRC
+
+Remark two things in the previous code:
+1. The fallback font is followed in this case by additional properties
+   that you need, =:mode=harf=, instructing LuaLaTeX to use a special
+   glyph renderer (harfbuzz).  This is not always needed.  This
+   feature is completely described in
+   [[https://mirrors.mit.edu/CTAN/macros/luatex/generic/luaotfload/luaotfload-latex.pdf][the
+   luaotfload package manual]].
+2. You can add fallback fonts for more Emacs scripts by adding
+   elements to the =:fallback= property, which is an associative list:
+   #+BEGIN_SRC emacs-lisp
+     ("main" :font "Latin Modern Roman"
+       :fallback (("emoji" . "Noto Color Emoji:mode=harf")
+                  ("han"   . "Noto Serif CJK JP")
+                  ("kana"  . "Noto Serif CJK JP")))
+   #+END_SRC
+3. The font names reported by Org are the font names used by
+   =lualatex=. They don't include the spaces used in the system font
+   names.
+4. There is the notorious exception of the ~Latin Modern~ fonts, which
+   are reported abbreviated as ~lm...~.
+
 ** Markdown Export
 :PROPERTIES:
 :DESCRIPTION: Exporting to Markdown.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 03057cbd0..b152d0e98 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -42,10 +42,32 @@ assume that the outer list has length one, which means ~(car
 (org-babel-tangle-single-block ...))~ is not guaranteed to return
 the correct ~(FILE . ...)~ cons.
 
+*** ox-beamer: New LaTeX preamble sequence
+
+When exporting to Beamer, all theme-related configuration will be
+generated right after the document class declaration in concordance
+with the examples shipped with the Beamer class.  Previously, the
+theme configuration went after the whole LaTeX preamble and the beamer
+frame definition, if it was needed.
 ** New features
 
 # We list the most important features, and the features that may
 # require user action to be used.
+*** New keyword BEAMER_THEME_PRE
+
+New keyword to add arbitrary LaTeX before the Beamer theme is loaded.
+
+*** Included ~org-latex-fontspec-config~
+
+This branch includes the fontspec package management code from
+feature/all-tex-fonts. The fontspec package can be used with lualatex
+and xelatex and its configuration controlled with
+~org-latex-fontspec-config~.
+
+*** Add ~org-latex-default-fontspec-features~ and keyword LATEX_FONTSPEC_DEFAULTS
+
+Add this variable with a *new* keyword ~#+LATEX_FONTSPEC_DEFAULTS~ to
+control default features when using the fontspec package.
 
 *** Column view now has quick help
 
@@ -567,6 +589,17 @@ Runtime error near line 2: attempt to write a readonly database (8)
 [ Babel evaluation exited with code 1 ]
 #+end_example
 
+*** New font handling in LaTeX exports
+
+The packages imported to handle fonts when producing PDF documents
+through LaTeX when using ~lualatex~ or ~xetex~ as your LaTeX compiler.
+
+- The ~amsmath~ package has been replaced by ~unicode-math~:
+  ~unicode-math~ is a full replacement for ~amsmath~ and most users
+  will not need to change anything. However, there is a small chance
+  of smaller typographical differences or even broken exports due to
+  issues in ~unicode-math~.
+
 ** New and changed options
 
 # Changes dealing with changing default values of customizations,
@@ -890,6 +923,14 @@ For performance, if the invisibility spec has been constructed, it can
 be passed in as ~invisibility-spec~ instead of having it be
 constructed again.
 
+*** New function ~org-latex--get-doc-scripts~
+
+This function returns a list of all the char-scripts used in the
+current buffer as strings.
+
+Adapted from the [[https://list.orgmode.org/orgmode/[email protected]/][discussion in the mailing list]] about fallbacks for
+non-latin scripts.
+
 ** Removed or renamed functions and variables
 
 *** Obsolete functions and variables removed from ~org-datetree~
diff --git a/lisp/org.el b/lisp/org.el
index 294a5eb52..4f0ef8de9 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -3641,9 +3641,10 @@ (defun org-get-packages-alist (var)
 
 (defcustom org-latex-default-packages-alist
   '(;; amsmath before fontspec for lualatex and xetex
-    (""     "amsmath"   t ("lualatex" "xelatex"))
-    ;; fontspec ASAP for lualatex and xetex
+    ;; (""     "amsmath"   t ("lualatex" "xelatex"))
+    ;; fontspec ASAP and unicode-ath for lualatex and xetex
     (""     "fontspec"  t ("lualatex" "xelatex"))
+    (""     "unicode-math"   t ("lualatex" "xetex"))
     ;; inputenc and fontenc are for pdflatex only
     ("AUTO" "inputenc"  t ("pdflatex"))
     ("T1"   "fontenc"   t ("pdflatex"))
@@ -3666,8 +3667,9 @@ (defcustom org-latex-default-packages-alist
 Org mode to function properly:
 
 - fontspec: for font and character selection in lualatex and xetex
-- inputenc, fontenc:  for basic font and character selection
-  in pdflatex
+- unicode-math: for maths in lualatex and xetex
+- inputenc, fontenc: for basic font and character selection in
+  pdflatex
 - graphicx: for including images
 - longtable: For multipage tables
 - wrapfig: for figure placement
diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el
index 4ebfca1f9..ce8e133b1 100644
--- a/lisp/ox-beamer.el
+++ b/lisp/ox-beamer.el
@@ -266,6 +266,7 @@ (org-export-define-derived-backend 'beamer 'latex
     (:latex-class "LATEX_CLASS" nil "beamer" t)
     (:beamer-subtitle-format nil nil org-beamer-subtitle-format)
     (:beamer-column-view-format "COLUMNS" nil org-beamer-column-view-format)
+    (:beamer-theme-pre "BEAMER_THEME_PRE" nil nil newline)
     (:beamer-theme "BEAMER_THEME" nil org-beamer-theme)
     (:beamer-color-theme "BEAMER_COLOR_THEME" nil nil t)
     (:beamer-font-theme "BEAMER_FONT_THEME" nil nil t)
@@ -861,6 +862,43 @@ (defun org-beamer-radio-target (radio-target text info)
 
 ;;;; Template
 ;;
+(defun org-beamer--create-theme (info)
+  "Create the theme contro code from INFO."
+  ;; Factored out of org-beamer-template
+  ;; Insert themes.
+  (let ((pre-header (plist-get info :beamer-theme-pre))
+        (format-theme
+	 (lambda (prop command)
+	   (let ((theme (plist-get info prop)))
+	     (when theme
+	       (concat command
+		       (if (not (string-match "\\[.*\\]" theme))
+			   (format "{%s}\n" theme)
+			 (format "%s{%s}\n"
+				 (match-string 0 theme)
+				 (org-trim
+				  (replace-match "" nil nil theme))))))))))
+    (concat
+     pre-header (and pre-header "\n")
+     (mapconcat (lambda (args) (apply format-theme args))
+	        '((:beamer-theme "\\usetheme")
+		  (:beamer-color-theme "\\usecolortheme")
+		  (:beamer-font-theme "\\usefonttheme")
+		  (:beamer-inner-theme "\\useinnertheme")
+		  (:beamer-outer-theme "\\useoutertheme"))
+	        ""))))
+
+(defun org-beamer--insert-theme-config (contents theme-info)
+  "Insert THEME-INFO right after the document class in CONTENTS."
+  (if theme-info
+      (save-match-data
+        (if (string-match "\\\\documentclass\\[.+?]{beamer}\n" contents) ;; just for beamer
+            (let* ((old-string (match-string-no-properties 0 contents))
+                   (new-string (concat old-string theme-info)))
+              (string-replace old-string new-string contents))
+          contents))
+    contents))
+
 ;; Template used is similar to the one used in `latex' backend,
 ;; excepted for the table of contents and Beamer themes.
 
@@ -876,31 +914,13 @@ (defun org-beamer-template (contents info)
 	  (format-time-string "%% Created %Y-%m-%d %a %H:%M\n"))
      ;; LaTeX compiler
      (org-latex--insert-compiler info)
-     ;; Document class and packages.
-     (org-latex-make-preamble info)
+     (org-beamer--insert-theme-config ;; Insert the theme right after the document class
+      (org-latex-make-preamble info) ;; Document class and packages.
+      (org-beamer--create-theme info)) ;; Theme-related header section
      ;; Define the alternative frame environment, if needed.
      (when (plist-get info :beamer-define-frame)
        (format "\\newenvironment<>{%s}[1][]{\\begin{frame}#2[environment=%1$s,#1]}{\\end{frame}}\n"
                org-beamer-frame-environment))
-     ;; Insert themes.
-     (let ((format-theme
-	    (lambda (prop command)
-	      (let ((theme (plist-get info prop)))
-		(when theme
-		  (concat command
-			  (if (not (string-match "\\[.*\\]" theme))
-			      (format "{%s}\n" theme)
-			    (format "%s{%s}\n"
-				    (match-string 0 theme)
-				    (org-trim
-				     (replace-match "" nil nil theme))))))))))
-       (mapconcat (lambda (args) (apply format-theme args))
-		  '((:beamer-theme "\\usetheme")
-		    (:beamer-color-theme "\\usecolortheme")
-		    (:beamer-font-theme "\\usefonttheme")
-		    (:beamer-inner-theme "\\useinnertheme")
-		    (:beamer-outer-theme "\\useoutertheme"))
-		  ""))
      ;; Possibly limit depth for headline numbering.
      (let ((sec-num (plist-get info :section-numbers)))
        (when (integerp sec-num)
diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el
index 8e2f72209..00911b031 100644
--- a/lisp/ox-latex.el
+++ b/lisp/ox-latex.el
@@ -173,6 +173,7 @@ (org-export-define-backend 'latex
     (:latex-compiler "LATEX_COMPILER" nil org-latex-compiler)
     (:latex-descriptive-env "LATEX_DESCRIPTIVE_ENV" nil org-latex-descriptive-environment)
     (:latex-use-sans nil "latex-use-sans" org-latex-use-sans)
+    (:latex-fontspec-defaults "LATEX_FONTSPEC_DEFAULTS" nil org-latex-fontspec-default-features)
     ;; Redefine regular options.
     (:date "DATE" nil "\\today" parse)))
 
@@ -1588,6 +1589,41 @@ (defcustom org-latex-toc-include-unnumbered nil
   :type 'boolean
   :safe #'booleanp)
 
+(defcustom org-latex-fontspec-config nil
+  "An alist with the configuration for the fonstpec package.
+
+This configuration will be generated when using lualatex or xelatex.
+
+Each element is defined as
+(`font-name' . `font-plist')
+ where `font-name' one of \"main\", \"sans\", \"mono\" or \"math\"
+ and `font-plist' is a plist. The keys for this plist are
+  `:font':     font name for font installed in your system
+  `:features': string or list of strings with font features (optional).
+  `:fallback': an alist of (`script' . `mapping') to map _Emacs_ script names
+               to their fallback font (optional). The exporter will warn you
+               about scripts in your document that need a fallback font."
+
+  :group 'org-export-latex
+  :package-version '(Org . "10.0")
+  :type 'alist
+  :safe  #'listp
+)
+
+(defcustom org-latex-fontspec-default-features nil
+  "This variable stores the default features for the fontspec package.
+When `nil', no default features are assumed.
+Else is is a comma-separeted string of \"feature=value\" that generates
+
+\\defaultfontfeatures{feature=value,...}
+
+in the LaTeX preamble after the fontspec configuration.
+"
+  :group 'org-export-latex
+  :package-version '(Org . "10.0")
+  :type '(choice (const :tag "No template" nil)
+		 (string :tag "Default font features"))
+  :safe #'string-or-null-p)
 
 ;;; Internal Functions
 
@@ -1827,7 +1863,154 @@ (defun org-latex-guess-polyglossia-language (header info)
 		  languages
 		  ""))
 	 t t header 0)))))
+;;
+;;
+(defun org-latex--get-doc-scripts ()
+  "This function gets the char-scripts used in the current buffer.
+Returns a list of strings with the char-scripts.
 
+TODO: Ignore commented out test
+
+Initial version proposed by Juan Manuel Macías in
+https://list.orgmode.org/orgmode/[email protected]/
+"
+  (let ((scripts))
+    (save-excursion
+      (goto-char (point-min))
+      (while
+          (re-search-forward "\\([^\u0000-\u007F\u0080-\u00FF\u0100-\u017F]\\)" nil t)
+        (let ((script (aref char-script-table
+                            (string-to-char (match-string 1)))))
+          (cl-pushnew (prin1-to-string script) scripts :test #'string=))))
+    scripts))
+;;
+;;
+(defun org-latex--parse-font-misses (buffer)
+  "Print missing font information in the Messages buffer.
+BUFFER is the buffer containing output.
+
+Print a list fonts and the _Emacs_ scripts they don't support.
+This list can be then used to provide/improve the font configurations.
+
+FIXME: checkwhat happens with XeLaTeX."
+  (with-current-buffer buffer
+    ;; Save excursion so that other functions continue
+    ;; to receive point where they expect it.
+    (save-excursion
+      (goto-char (point-min)) ;; start from the beginning
+      (when (re-search-forward "^[ \t]*This is .*?TeX.*?Version" nil t)
+        (let ((font-script nil))
+          ;; Create a flat alist with (FONT . EMACS_SCRIPT) for each miss detected
+           (while (search-forward-regexp "There is no \\(.\\) (U\\+\\([^)]+\\)) in font \\([^:]+\\)" nil t)
+            (let* ((failed-char (string-to-char (match-string 1)))
+                   ;; (unicode-value (match-string 2))
+                   (emacs-script (aref char-script-table failed-char)) ;; see org-latex--get-doc-scripts
+                   (failed-font (match-string 3)))
+              (setq emacs-script (prin1-to-string emacs-script))
+              (if (null font-script)
+                  (setq font-script (list (cons failed-font emacs-script)))
+                (push (cons failed-font emacs-script) font-script))))
+           ;; When all is OK, font-script will be nil
+           (unless (null font-script)
+             ;; Sort the list by FONT in place, i.e. font-script is modified by sort
+             ;; original definition of sort since Emacs25
+             (sort font-script #'(lambda (s1 s2)
+                                   (string< (car s1) (car s2))))
+             ;; Remove duplicates,
+             ;; (delete-dups) is destructive and will replace the original list automatically
+             (delete-dups font-script)
+             (let ((current-font "")
+                   (result ""))
+               (cl-loop for (font . script) in font-script
+                        do
+                        (if (string= current-font font)
+                            ;; Collect the EMACS_SCRIPT
+                            (setq result (concat result " " script))
+                          (progn
+                            (setq current-font font)
+                            ;; New line for each new FONT
+                            (setq result (concat result "\nFor font " font ": " script)))))
+               (warn "Missing scripts:%s" result))))))))
+;;
+;;
+(defun org-latex--fontspec-prelude (compiler)
+  "Return the fontspec configurtion for the current buffer as a string.
+
+If COMPILER is \"xelatex\", omit fallback font detection."
+  (let ((doc-scripts (org-latex--get-doc-scripts))
+        (fontspec-config org-latex-fontspec-config)
+        (fallback-found)  ;; a list of fallbacks that are really needed
+        (fallback-alist)) ;; an alist (font_name . fallback-name)
+
+    (message "Fonts detected: %s" doc-scripts)
+    (with-temp-buffer
+      ;; add all fonts with fallback to fallback-alist for lualatex
+      ;; leave empty for xelatex
+      (when (string= compiler "lualatex")
+        (dolist (fconfig fontspec-config)
+          (when-let* ((fname (car fconfig))
+                      (config-plist (cdr fconfig))
+                      (fallback (plist-get config-plist :fallback)))
+            (push (cons fname (concat "fallback_" fname)) fallback-alist))))
+      ;; (message "fallback-alist ==> %s" fallback-alist)
+      (when fallback-alist ;; if there are fonts with fallbacks (and we use lualatex)
+        (let ((directlua nil)) ;; Did we write the beginning of this block?
+          ;; create the directlua header
+          (dolist (fallback fallback-alist)
+            ;; (message "fallback ===> %s" fallback)
+            (when-let*
+                ((fbf-fname (car fallback))
+                 (fbf-name (cdr fallback))
+                 (fbf-plist (alist-get fbf-fname fontspec-config nil nil #'string=))
+                 (fbf-flist (plist-get fbf-plist :fallback)))
+              ;; collect all falbacks for scripts that are present in the doc
+              (let ((fallback-flist
+                     (cl-loop for fpair in fbf-flist
+                              ;; check (car fpair) is in document scripts
+                              ;; and the fallback is not already in the result
+                              when (and (member-ignore-case (car fpair) doc-scripts)
+                                        (null (member-ignore-case (cdr fpair) fresult)))
+                              collect (cdr fpair) into fresult
+                              finally return fresult)))
+                ;; (message "fallback-flist ==> %s" fallback-flist)
+                (when fallback-flist
+                  (unless directlua ;; add the heading before the first lua block
+                    (insert "\\directlua{\n")
+                    (setq directlua t))
+                  ;; (setq fallback-flist (cl-remove-duplicates fallback-flist
+                  ;;                                            :test #'string=))
+                  (setq fallback-found (append fallback-found (list fbf-name)))
+                  (insert (format " luaotfload.add_fallback (\"%s\",{\n" fbf-name))
+                  ;; Here we get the font fallbacks list
+                  (dolist (fname fallback-flist)
+                    ;; TODO; when (car fpair) in document charsets
+                    (insert (format "  \"%s\",\n" fname)))
+                  (insert " })\n")))))
+          (when directlua ;; if we have found any lua fallbacks, close the lua block
+            (insert "}\n"))))
+      ;; (message "fallbacks: %s" fallback-alist)
+      (dolist (fpair fontspec-config)
+        (when-let* ((ffamily (car fpair))
+                    (fplist  (cdr fpair))
+                    (ffont (plist-get fplist :font)))
+          (insert (format "\\set%sfont{%s}" ffamily ffont))
+          ;; add the extra features
+          (let ((ffeatures (plist-get fplist :props)))
+            (when (stringp ffeatures)
+              (setq ffeatures (list ffeatures))) ;; needs to be a list to concat a possible fallback
+            ;; (message "--> ffeatures: %s" ffeatures)
+            (when-let* ((fallback-fn (alist-get ffamily fallback-alist nil nil #'string=))
+                        (fallback-test (member fallback-fn fallback-found)) ;; nil if not found -> don't add to ffeatures
+                        (fallback-spec (format "RawFeature={fallback=%s}" fallback-fn)))
+              (setq ffeatures (cl-concatenate #'list ffeatures (list fallback-spec))))
+            ;; (message "ffeatures %s" ffeatures)
+            (when ffeatures
+              (insert (format "[%s]" (mapconcat #'identity ffeatures ",")))))
+          (insert "\n")))
+      (buffer-string))))
+
+;;
+;;
 (defun org-latex--remove-packages (pkg-alist info)
   "Remove packages based on the current LaTeX compiler.
 
@@ -2061,6 +2244,8 @@ (defun org-latex-make-preamble (info &optional template snippet?)
 specified in `org-latex-default-packages-alist' or
 `org-latex-packages-alist'."
   (let* ((class (plist-get info :latex-class))
+         (compiler (plist-get info :latex-compiler))
+         (fontspec-defaults (plist-get info :latex-fontspec-defaults))
 	 (class-template
 	  (or template
 	      (let* ((class-options (org-latex--mk-options (plist-get info :latex-class-options)))
@@ -2086,13 +2271,20 @@ (defun org-latex-make-preamble (info &optional template snippet?)
 	 (org-latex--remove-packages org-latex-packages-alist info)
 	 snippet?
 	 (mapconcat #'org-element-normalize-string
-		    (list (plist-get info :latex-header)
+		    (list (and (not snippet?)
+                               (member compiler '("lualatex" "xelatex"))
+                               org-latex-fontspec-config
+                               (org-latex--fontspec-prelude compiler))
+                          (and (not snippet?)
+                               (member compiler '("lualatex" "xelatex"))
+                               fontspec-defaults
+                               (format "\\defaultfontfeatures{%s}\n" fontspec-defaults))
+                          (plist-get info :latex-header)
 			  (and (not snippet?)
 			       (plist-get info :latex-header-extra))
                           (and (not snippet?)
                                (plist-get info :latex-use-sans)
-                               "\\renewcommand*\\familydefault{\\sfdefault}"))
-
+                               "\\renewcommand*\\familydefault{\\sfdefault}\n"))
 		    ""))))
       info)
      info)))
@@ -4632,6 +4824,7 @@ (defun org-latex-compile--postprocess (outfile log-buf &optional snippet)
                      "\\(?:\\.[0-9]+\\)?\\."
                      (regexp-opt org-latex-logfiles-extensions))
              t)))
+    (org-latex--parse-font-misses log-buf)
     (let ((warnings (org-latex--collect-warnings log-buf)))
       (funcall
        (if warnings
diff --git a/testing/lisp/test-ox-beamer.el b/testing/lisp/test-ox-beamer.el
index 482f22c8f..72cfaedb6 100644
--- a/testing/lisp/test-ox-beamer.el
+++ b/testing/lisp/test-ox-beamer.el
@@ -111,5 +111,35 @@ (ert-deftest test-ox-beamer/orgframe-in-one-example ()
      (should (search-forward (concat "\\end{frame}") nil t))
      (should (search-forward (concat "\\end{" org-beamer-frame-environment "}"))))))
 
+;; fontspec
+;;; Although in-depth testing is done for ox-latex.el, a basic test is included here:
+
+(ert-deftest test-ox-beamer/fontspec ()
+  "Test that `org-latex-fontspec-config' is used.
+The theme prelue should be just after document class and before the theme info.
+Then we would have fontspec and finally the fonts."
+  (let ((org-latex-fontspec-config '(("main" :font "FreeSerif")
+                                     ("mono" :font "FreeMono"))))
+   (org-test-with-exported-text
+      'beamer
+      "#+OPTIONS: toc:nil
+#+LATEX_COMPILER: lualatex
+#+BEAMER_THEME_PRE: \\usepackage{geometry}
+
+* A frame
+- with text
+- with more text
+"
+      ;; (message "fontspec:\n%s" (buffer-string))
+      (goto-char (point-min))
+      (should (search-forward "\\documentclass[" nil t))
+      (should (search-forward "\\usepackage{geometry}" nil t))
+      (should (search-forward "\\usetheme{default}" nil t))
+      (should (search-forward "\\usepackage{fontspec}" nil t))
+      (should (search-forward "\\setmainfont{FreeSerif}" nil t))
+      (should (search-forward "\\setmonofont{FreeMono}" nil t))
+      (should (search-forward "\\begin{document}" nil t)))))
+
+
 (provide 'test-ox-beamer)
 ;;; test-ox-beamer.el ends here
diff --git a/testing/lisp/test-ox-latex.el b/testing/lisp/test-ox-latex.el
index a804e281b..10534bca4 100644
--- a/testing/lisp/test-ox-latex.el
+++ b/testing/lisp/test-ox-latex.el
@@ -522,5 +522,136 @@ (ert-deftest test-ox-latex/subtree-export-with-language ()
        (should (search-forward "\\usepackage[utf8]{inputenc} \\usepackage[french, english]{babel}")))
      (kill-buffer export-buffer))))
 
+;; fontspec tests
+
+(ert-deftest test-ox-latex/lualatex-fontspec-plain ()
+  "Test that neither defaultfontfeatures nor a directlua block are created
+when no fallbacks in fontspec configuration."
+  (let ((org-latex-compiler "lualatex")
+        (org-latex-fontspec-config '(("main" :font "FreeSans"))))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: fontspec
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text.
+"
+     ;; (message "--> %s" (buffer-string))
+     (goto-char (point-min))
+     (should-not (search-forward "\\defaultfontfeatures{" nil t))
+     (goto-char (point-min))
+     (should-not (search-forward "\\directlua" nil t))
+     (goto-char (point-min))
+     (should (search-forward "\\setmainfont{FreeSans}" nil t)))))
+
+(ert-deftest test-ox-latex/lualatex-fontspec-features ()
+  "Test that font features are generated when declared."
+  (let ((org-latex-compiler "lualatex")
+        (org-latex-fontspec-config '(("main" :font "FreeSans")
+                                     ("mono" :font "FreeMono" :props "Scale=MatchLowercase"))))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: fontspec-features
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text.
+"
+     (goto-char (point-min))
+     (should (search-forward "\\setmainfont{FreeSans}" nil t))
+     (goto-char (point-min))
+     (should (search-forward "\\setmonofont{FreeMono}[Scale=MatchLowercase]" nil t)))))
+
+(ert-deftest test-ox-latex/lualatex-fontspec-directlua ()
+  "Test that directlua block is created"
+  (let ((org-latex-compiler "lualatex")
+        (org-latex-fontspec-config '(("main"
+                                      :font "FreeSerif"
+                                      :fallback (("emoji" . "Noto Color Emoji:mode=harf"))))))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: fontspec
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text with emoji: 👍
+"
+     (goto-char (point-min))
+     (should (search-forward "\\directlua" nil t))
+     (should (search-forward "Noto Color Emoji" nil t))
+     (should (search-forward "\\setmainfont{FreeSerif}[RawFeature={fallback=fallback_main}]" nil t)))))
+
+(ert-deftest test-ox-latex/lualatex-fontspec-noemoji ()
+  "Test that directlua block is not created because it is not needed
+no emojis detected"
+  (let ((org-latex-compiler "lualatex")
+        (org-latex-fontspec-config '(("main"
+                                      :font "FreeSerif"
+                                      :fallback (("emoji" . "Noto Color Emoji:mode=harf"))))))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: fontspec
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text without emojis.
+"
+     ;; (message "--> %s" (buffer-string))
+     (goto-char (point-min))
+     (should-not (search-forward "\\directlua" nil t))
+     (goto-char (point-min))
+     (should-not (search-forward "Noto Color Emoji" nil t))
+     (goto-char (point-min))
+     (should (search-forward "\\setmainfont{FreeSerif}" nil t)))))
+
+(ert-deftest test-ox-latex/lualatex-fontspec-default-features ()
+  "Test that fontspec default features are generated
+when fontspec is used in LaTeX document."
+  (let ((org-latex-compiler "lualatex")
+        (org-latex-fontspec-config '(("main" :font "FreeSerif")))
+        (org-latex-fontspec-default-features "Scale=MatchLowercase"))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: fontspec
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text without emojis.
+"
+     ;; (message "--> %s" (buffer-string))
+     (goto-char (point-min))
+     (should (search-forward "\\setmainfont{FreeSerif}" nil t))
+     (should (search-forward "\\defaultfontfeatures{Scale=MatchLowercase}" nil t)))))
+
+(ert-deftest test-ox-latex/lualatex-no-fontspec-default-features ()
+  "Test that fontspec related code is not generated
+when pdflatex is used to compile LaTeX document."
+  (let ((org-latex-compiler "pdflatex")
+        (org-latex-fontspec-config '(("main"
+                                      :font "FreeSerif")))
+        (org-latex-fontspec-default-features "Scale=MatchLowercase"))
+    (org-test-with-exported-text
+     'latex
+     "#+TITLE: no fontspec with pdflatex
+#+OPTIONS: toc:nil H:3 num:nil
+
+* Heading
+
+A random text without emojis.
+"
+     ;; (message "--> %s" (buffer-string))
+     (goto-char (point-min))
+     (should-not (search-forward "\\usepackage{fonstpec}" nil t))
+     (goto-char (point-min))
+     (should-not (search-forward "\\defaultfontfeatures{" nil t)))))
+
+
+
 (provide 'test-ox-latex)
 ;;; test-ox-latex.el ends here
-- 
2.43.0

Reply via email to