OK, I found the thread... This is a reset and a result of a lot of testing. Leave out the fonts for the moment being. *just* beamer and *just* themes...
The patch does the following 1.) create the THEME info by putting BEAMER_THEME_PRE before BEAMER_THEME 2.) insert the resulting THEME information directly after the document class, when this class is beamer. Particular points worth answering: > https://tex.stackexchange.com/questions/54577/how-do-i-customize-beamer-template You can move the usetheme directly after the documentclass and it compiles as is... > Take a look at > https://github.com/fniessen/refcard-org-beamer > There, users are advised to do the following > > (eval-after-load "ox-latex" > > ;; update the list of LaTeX classes and associated header (encoding, etc.) > ;; and structure > '(add-to-list 'org-latex-classes > `("beamer" > ,(concat "\\documentclass[presentation]{beamer}\n" > "[DEFAULT-PACKAGES]" > "[PACKAGES]" > "[EXTRA]\n") > ("\\section{%s}" . "\\section*{%s}") > ("\\subsection{%s}" . "\\subsection*{%s}") > ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))) All the theme stuff would go directly after the document class instead of the [EXTRA]. I didn't get any answer for my PR and don't know the reason. Best, /PA On Sat, 27 Jun 2026 at 07:52, Ihor Radchenko <[email protected]> wrote: > Ihor Radchenko <[email protected]> writes: > > > Pedro Andres Aranda Gutierrez <[email protected]> writes: > > > >> Hi again, a follow-up (after a lot of experimentation). > >> > >> There is a simpler solution than the FONTS_PRE there: > >> > >> Inserting FONTS between the PACKAGES and the LATEX_HEADER should be > enough > >> (or at least was enough even for the three complex documents I needed to > >> deal with). > > > > Hmm. I am confused. > > Where does BEAMER_THEME come in this picture? > > Ping. > > -- > Ihor Radchenko // yantar92, > Org mode maintainer, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> > -- 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 75a224bc75e93e57ac8fa44ede263d24902792b9 Mon Sep 17 00:00:00 2001 From: "Pedro A. Aranda" <[email protected]> Date: Sun, 5 Jul 2026 10:51:08 +0200 Subject: [PATCH] ox-beamer: new BEAMER_THEME_PRE keyword Allow inserting LaTeX code before the theme information. Rationale of this change is that we move the theme information right after the document class for beamer, so that our settings from the (default-)latex-packages-alists take effect correctly. * doc/org-manual.org: Document new keyword LATXE_BEAMER_PRE. * etc/ORG-NEWS: Announce new keyword LATXE_BEAMER_PRE. * lisp/ox-beamer.el (org-beamer--theme-settings): Factor out theme prelude generation. (org-beamer--insert-theme-info): Insert theme info immediatelly after "\\documentclass[options]{beamer}". (org-beamer-template): use factored out functions. * testing/lisp/test-ox-beamer.el (test-ox-beamer/org-beamer-pre-theme): New test to check the theme position with in the LaTeX prelude. --- doc/org-manual.org | 13 ++ etc/ORG-NEWS | 5 + lisp/ox-beamer.el | 218 +++++++++++++++++++-------------- testing/lisp/test-ox-beamer.el | 188 ++++++++++++++++++++++++++++ 4 files changed, 332 insertions(+), 92 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 96de73975..99929dcae 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -13173,6 +13173,19 @@ settings (see [[*Export Settings]]). #+cindex: @samp{BEAMER_OUTER_THEME}, keyword The Beamer outer theme. +- =BEAMER_THEME_PRE= :: + + #+cindex: @samp{BEAMER_THEME_PRE}, keyword + Arbitrary lines inserted in the preamble, just before the Beamer + theme is defined with =\usetheme{}=. This can used, for example, to + import the + [[https://mirrors.ctan.org/macros/latex/contrib/geometry/geometry.pdf][=geometry= + package]] to fine-tune the presentation page size. For example: + + : #+BEAMER_THEME_PRE: \usepackage{geometry} + : #+BEAMER_THEME_PRE: \geometry{paperwidth=160mm, paperheight=90mm} + : #+BEAMER_THEME: Boadilla + - =BEAMER_HEADER= :: #+cindex: @samp{BEAMER_HEADER}, keyword diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 22cc760d8..35276a015 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -190,6 +190,11 @@ can be also written as: ,#+LATEX_CLASS_OPTIONS: a4paper,12pt #+END_SRC +*** New option BEAMER_THEME_PRE + +New option ~#+BEAMER_THEME_PRE~ allows you to include arbitrary LaTeX +code just before defining the Beamer theme. + *** Updated custom variable ~org-babel-update-intermediate~ The variable is now a custom variable, and it can take a new value: diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el index 4ebfca1f9..635da3e14 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,109 +862,142 @@ (defun org-beamer-radio-target (radio-target text info) ;;;; Template ;; +;; Extract theme related information +;; +(defun org-beamer--theme-settings (info) + "Return the code related to the Beamer theme for the latex preamble +or nil if the latex class is not \"beamer\". + +INFO is a plist holding the export options." + (when (equal (plist-get info :latex-class) "beamer") + (let ((pre-header (plist-get info :beamer-theme-pre)) + (format-theme + (lambda (prop command) + (save-match-data + (let ((theme (plist-get info prop))) + (when theme + (concat "\\use" command ;; escape for regexp-replace + (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 "theme") + (:beamer-color-theme "colortheme") + (:beamer-font-theme "fonttheme") + (:beamer-inner-theme "innertheme") + (:beamer-outer-theme "outertheme")) + ""))))) + ;; Template used is similar to the one used in `latex' backend, ;; excepted for the table of contents and Beamer themes. +(defun org-beamer--insert-theme-info (template theme-info) + "Return the beamer TEMPLATE with the THEME-INFO inserted +directly after document class declaration. + +The theme info will be inserted when the template +1. doesn't include any theme configuration, and +2. it uses the the beamer document class." + (when (and theme-info + template + (not (string-match-p "^\\\\usetheme{" template))) + (save-match-data + (when (string-match + "\\(^\\\\documentclass\\(\\[.*?\\]\\)?{beamer}\n\\)" template) + (let ((doc-class (match-string 1 template))) + (setq template (string-replace doc-class + (concat doc-class theme-info) + template)))))) + template) ;; just in case the theme information is nil + (defun org-beamer-template (contents info) "Return complete document string after Beamer conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options." (let ((title (org-export-data (plist-get info :title) info)) - (subtitle (org-export-data (plist-get info :subtitle) info))) + (subtitle (org-export-data (plist-get info :subtitle) info)) + (latex-class (plist-get info :latex-class))) + ;; (concat - ;; Timestamp. - (and (plist-get info :time-stamp-file) - (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) - ;; Define the alternative frame environment, if needed. - (when (plist-get info :beamer-define-frame) + ;; Timestamp. + (and (plist-get info :time-stamp-file) + (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) + ;; LaTeX compiler + (org-latex--insert-compiler info) + ;; Document class, theme and packages. + (org-beamer--insert-theme-info (org-latex-make-preamble info) + (org-beamer--theme-settings info)) + ;; 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) - (format "\\setcounter{secnumdepth}{%d}\n" sec-num))) - ;; Author. - (let ((author (and (plist-get info :with-author) - (let ((auth (plist-get info :author))) - (and auth (org-export-data auth info))))) - (email (and (plist-get info :with-email) - (org-export-data (plist-get info :email) info)))) - (cond ((and author email (not (string= "" email))) - (format "\\author{%s\\thanks{%s}}\n" author email)) - ((or author email) (format "\\author{%s}\n" (or author email))))) - ;; Date. - (let ((date (and (plist-get info :with-date) (org-export-get-date info)))) - (format "\\date{%s}\n" (org-export-data date info))) - ;; Title - (format "\\title{%s}\n" title) - (when (org-string-nw-p subtitle) - (concat (format (plist-get info :beamer-subtitle-format) subtitle) "\n")) - ;; Beamer-header - (let ((beamer-header (plist-get info :beamer-header))) - (when beamer-header - (format "%s\n" (plist-get info :beamer-header)))) - ;; 9. Hyperref options. - (let ((template (plist-get info :latex-hyperref-template))) - (and (stringp template) - (format-spec template (org-latex--format-spec info)))) - ;; engrave-faces-latex preamble - (when (and (eq (plist-get info :latex-src-block-backend) 'engraved) - (org-element-map (plist-get info :parse-tree) - '(src-block inline-src-block) #'identity - info t)) - (org-latex-generate-engraved-preamble info)) - ;; Document start. - "\\begin{document}\n\n" - ;; Title command. - (org-element-normalize-string - (cond ((not (plist-get info :with-title)) nil) - ((string= "" title) nil) - ((not (stringp org-latex-title-command)) nil) - ((string-match "\\(?:[^%]\\|^\\)%s" - org-latex-title-command) - (format org-latex-title-command title)) - (t org-latex-title-command))) - ;; Table of contents. - (let ((depth (plist-get info :with-toc))) - (when depth - (concat - (format "\\begin{frame}%s{%s}\n" - (org-beamer--normalize-argument - (plist-get info :beamer-outline-frame-options) 'option) - (plist-get info :beamer-outline-frame-title)) - (when (wholenump depth) - (format "\\setcounter{tocdepth}{%d}\n" depth)) - "\\tableofcontents\n" - "\\end{frame}\n\n"))) - ;; Document's body. - contents - ;; Creator. - (if (plist-get info :with-creator) - (concat (plist-get info :creator) "\n") - "") + ;; Possibly limit depth for headline numbering. + (let ((sec-num (plist-get info :section-numbers))) + (when (integerp sec-num) + (format "\\setcounter{secnumdepth}{%d}\n" sec-num))) + ;; Author. + (let ((author (and (plist-get info :with-author) + (let ((auth (plist-get info :author))) + (and auth (org-export-data auth info))))) + (email (and (plist-get info :with-email) + (org-export-data (plist-get info :email) info)))) + (cond ((and author email (not (string= "" email))) + (format "\\author{%s\\thanks{%s}}\n" author email)) + ((or author email) (format "\\author{%s}\n" (or author email))))) + ;; Date. + (let ((date (and (plist-get info :with-date) (org-export-get-date info)))) + (format "\\date{%s}\n" (org-export-data date info))) + ;; Title + (format "\\title{%s}\n" title) + (when (org-string-nw-p subtitle) + (concat (format (plist-get info :beamer-subtitle-format) subtitle) "\n")) + ;; Beamer-header + (let ((beamer-header (plist-get info :beamer-header))) + (when beamer-header + (format "%s\n" (plist-get info :beamer-header)))) + ;; 9. Hyperref options. + (let ((template (plist-get info :latex-hyperref-template))) + (and (stringp template) + (format-spec template (org-latex--format-spec info)))) + ;; engrave-faces-latex preamble + (when (and (eq (plist-get info :latex-src-block-backend) 'engraved) + (org-element-map (plist-get info :parse-tree) + '(src-block inline-src-block) #'identity + info t)) + (org-latex-generate-engraved-preamble info)) + ;; Document start. + "\\begin{document}\n\n" + ;; Title command. + (org-element-normalize-string + (cond ((not (plist-get info :with-title)) nil) + ((string= "" title) nil) + ((not (stringp org-latex-title-command)) nil) + ((string-match "\\(?:[^%]\\|^\\)%s" + org-latex-title-command) + (format org-latex-title-command title)) + (t org-latex-title-command))) + (let ((depth (plist-get info :with-toc))) + (when depth + (concat + (format "\\begin{frame}%s\nframetitle{%s}\n" + (org-beamer--normalize-argument + (plist-get info :beamer-outline-frame-options) 'option) + (plist-get info :beamer-outline-frame-title)) + (when (wholenump depth) + (format "\\setcounter{tocdepth}{%d}\n" depth)) + "\\tableofcontents\n" + "\\end{frame}\n\n"))) + ;; Document's body. + contents + ;; Creator. + (if (plist-get info :with-creator) + (concat (plist-get info :creator) "\n") + "") ;; Document end. "\\end{document}"))) diff --git a/testing/lisp/test-ox-beamer.el b/testing/lisp/test-ox-beamer.el index 482f22c8f..bba8a2533 100644 --- a/testing/lisp/test-ox-beamer.el +++ b/testing/lisp/test-ox-beamer.el @@ -111,5 +111,193 @@ (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 "}")))))) +(ert-deftest test-ox-beamer/org-beamer-pre-theme () + "Test that the theme is in its new place and beamer-pre is included." + (org-test-with-exported-text + 'beamer + "#+OPTIONS: toc:nil +#+LATEX_CLASS_OPTIONS: presentation,t +#+BEAMER_THEME_PRE: \\usepackage{geometry} +#+BEAMER_THEME: Boadilla +* A frame +Here is an example. +" + (goto-char (point-min)) + (should (search-forward "\\documentclass[presentation,t]{beamer} +\\usepackage{geometry} +\\usetheme{Boadilla}\n")))) + +(ert-deftest test-ox-beamer/org-beamer-test-fonts () + "Test that fonts are defined correctly." + (let ((org-latex-compiler "lualatex") + (org-latex-default-packages-alist nil) + (org-latex-packages-alist nil) + (org-latex-hyperref-template nil) + (org-latex-fontspec-config '(("main" :font "FreeSerif") ("mono" :font "FreeSans")))) + (org-test-with-exported-text + 'beamer + "#+OPTIONS: toc:nil +#+LATEX_CLASS_OPTIONS: presentation,t +#+BEAMER_THEME: Boadilla +* A frame +Here is an example. +" + ;; (message "==> %s" (buffer-string)) + (goto-char (point-min)) + (should (search-forward "\\documentclass[presentation,t]{beamer}")) + (should (search-forward "\\usetheme{Boadilla}")) + (should (search-forward "\\usepackage{fontspec}")) + (should (search-forward "\\setmainfont{FreeSerif}"))))) + +(ert-deftest test-ox-beamer/org-beamer-no-replace-theme () + "Test that the document class options are changed but the +theme info is not if it comes in the template." + (let ((org-latex-classes '(("beamer" "\\documentclass[presentation,t]{beamer} +\\usetheme{Madrid}\n")))) + (org-test-with-exported-text + 'beamer + "#+OPTIONS: toc:nil +#+LATEX_CLASS_OPTIONS: handout,12pt,t +#+BEAMER_THEME: Boadilla +* A frame +Here is an example. +" + (goto-char (point-min)) + (should (search-forward "\\documentclass[handout,12pt,t]{beamer}" nil t)) + (save-excursion + (should-not (search-forward "\\usetheme{Boadilla}" nil t))) + (should (search-forward "\\usetheme{Madrid}" nil t))))) + +(ert-deftest test-ox-beamer/org-beamer-ltx-talk () + "Test that we can handle ltx-talk." + (let ((org-latex-hyperref-template nil) + (org-latex-default-packages-alist nil) + (org-latex-packages-alist nil)) + (org-test-with-exported-text + 'beamer + "#+STARTUP: beamer +#+LATEX_COMPILER: lualatex +#+LATEX_CLASS: ltx-talk +#+LATEX_CLASS_OPTIONS: +#+BEAMER_THEME: Boadilla +#+TITLE: A quick an dirty test +#+OPTIONS: toc:nil + +* A frame +@@beamer: \\large@@ +- First +- Second +- Third +" + ;; (message "==> TESTTING ltx-talk:\n%s" (buffer-string)) + (goto-char (point-min)) + (should (search-forward "\\documentclass{ltx-talk}" nil t)) + (save-excursion + (should-not (search-forward "\\usetheme{Boadilla}" nil t))) + ;; Check that labels are added as ltx-talk expects them + (should (search-forward "\\begin{frame}[name = ")) + (should (search-forward "\\frametitle{A frame}")) + (should (search-forward "\\begin{itemize}")) + (should (search-forward "\\item Second"))))) + +(ert-deftest test-ox-beamer/fail-ltx-talk-class () + "Test that it bails out if compiler is not lualates for `ltx-talk'" + :expected-result :failed + (let ((org-latex-compiler "pdflatex") + (org-latex-hyperref-template nil) + (org-latex-packages-alist nil) + (org-latex-default-packages-alist nil)) + (org-test-with-exported-text + 'beamer + "#+STARTUP: beamer +#+OPTIONS: toc:nil +#+LATEX_CLASS: ltx-talk +#+LATEX_CLASS_OPTIONS: +#+TITLE: Testing =ltx-talk= +#+BEAMER_THEME: +* A frame +- First +- Second +- Third + +# Local variables: +# End: +" + (goto-char (point-min)) + (should (search-forward "\\documentclass" nil t))))) + +(ert-deftest test-ox-beamer/org-beamer-ltx-talk-listing () + "Test that we can handle verbatim in frames. + +ltx-talk will require frame* for frames with verbatim contents." + (let ((org-latex-hyperref-template nil) + (org-latex-default-packages-alist nil) + (org-latex-packages-alist nil)) + (org-test-with-exported-text + 'beamer + "#+STARTUP: beamer +#+LATEX_COMPILER: lualatex +#+LATEX_CLASS: ltx-talk +#+LATEX_CLASS_OPTIONS: mode=handout +#+BEAMER_THEME: Boadilla +#+TITLE: A quick an dirty test +#+OPTIONS: toc:nil H:2 + +* Listing test +** A frame +@@beamer: \\large@@ +- First +- Second +- Third +** The =SRC= frame +@@beamer: \\large@@ +#+BEGIN_SRC python +from sys import stderr +print(\"Hello world!\",file=stderr) +#+END_SRC +** The =EXAMPLE= frame +@@beamer: \\large@@ +#+BEGIN_EXAMPLE +,#+OPTIONS: toc:nil H:2 +#+END_EXAMPLE +" + ;; (message "==> TESTTING ltx-talk:\n%s" (buffer-string)) + (goto-char (point-min)) + (should (search-forward "\\documentclass[mode=handout]{ltx-talk}" nil t)) + (save-excursion + (should-not (search-forward "\\usetheme{Boadilla}" nil t))) + (should (search-forward "\\begin{frame}" nil t)) + (should (search-forward "\\begin{itemize}" nil t)) + (should (search-forward "\\item Second" nil t)) + (should (search-forward "\\begin{frame*}" nil t))))) + +(ert-deftest test-ox-beamer/use-ltx-talk-class () + "Test that `ltx-talk' is correctly handled is defined and used." + (let ((org-latex-compiler "lualatex") + (org-latex-hyperref-template nil) + (org-latex-packages-alist nil) + (org-latex-default-packages-alist nil)) + (org-test-with-exported-text + 'beamer + "#+STARTUP: beamer +#+OPTIONS: toc:nil +#+LATEX_CLASS: ltx-talk +#+LATEX_CLASS_OPTIONS: +#+TITLE: Testing =ltx-talk= +#+BEAMER_THEME: +* A frame +- First +- Second +- Third + +# Local variables: +# End: +" + ;; (message "--> \n%s" (buffer-string)) + (goto-char (point-min)) + (save-excursion (should-not (search-forward "\\usetheme{" nil t))) + (save-excursion (should-not (search-forward "[label=]" nil t))) + (save-excursion (should (search-forward "\\frametitle{A frame}" nil t)))))) + (provide 'test-ox-beamer) ;;; test-ox-beamer.el ends here -- 2.43.0
