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