I see. Nice example for the *latex* exporter.

However, would something like that be necessary for beamer or will the
default definition of org-latex-classes in ox-beamer be enough?

I wouldn't have dared to start this if I hadn't had a presentation where I
didn't get what I was reading in the org file.

Would it be worthwhile at least to try to agree on a basic flow of LaTeX
blocks for beamer?

Could you please try the features out by exporting them to a LaTeX buffer
and telling me if it doesn't agree +/- with what the authors of beamer tell
their users to do? Isn't that what the beamer exporter do too?

Anyhow, for what it might be worth, attached is a clean version of the
whole patch in one file...

/PA

PS: After my work on the all-tex-fonts branch I have started to realise
that there are too many trees that will not let us see the forest... Try to
seek to make things simpler...

On Sun, 12 Apr 2026 at 12:34, Ihor Radchenko <[email protected]> wrote:

> Pedro Andres Aranda Gutierrez <[email protected]> writes:
>
> > OK, what about the attached diff (on top of the others).
> > We introduce (yet another) prop to info and send everything
> > theme-related in it to
> > ox-latex.el
>
> That makes sense, although your diff will not always put things right
> after documentclass.
>
> > +      (header (nth 1 (assoc class (plist-get info :latex-classes))))
> > +         (pre-template
> > +          (if (stringp header) ;; is included in org-latex-classes
> > +           (if (not class-options) header
> > +             (replace-regexp-in-string
> > +              "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)"
> > +              class-options header t nil 1))
> > +            (user-error "Unknown LaTeX class `%s'" class))))
>
> Note that header may itself contain packages:
>
> The header string
> -----------------
>
> The HEADER-STRING is the header that will be inserted into the
> LaTeX file.  It should contain the \documentclass macro, and
> anything else that is needed for this setup.  To this header, the
> following commands will be added:
>
> - Calls to \usepackage for all packages mentioned in the
>   variables org-latex-default-packages-alist and
>   org-latex-packages-alist.  Thus, your header definitions
>   should avoid to also request these packages.
>
> - Lines specified via "#+LATEX_HEADER:" and
>   "#+LATEX_HEADER_EXTRA:" keywords.
>
> If you need more control about the sequence in which the header
> is built up, or if you want to exclude one of these building
> blocks for a particular class, you can use the following
> macro-like placeholders.
>
>  [DEFAULT-PACKAGES]      \usepackage statements for default packages
>  [NO-DEFAULT-PACKAGES]   do not include any of the default packages
>  [PACKAGES]              \usepackage statements for packages
>  [NO-PACKAGES]           do not include the packages
>  [EXTRA]                 the stuff from #+LATEX_HEADER(_EXTRA)
>  [NO-EXTRA]              do not include #+LATEX_HEADER(_EXTRA) stuff
>
> So a header like
>
>   \documentclass{article}
>   [NO-DEFAULT-PACKAGES]
>   [EXTRA]
>   \providecommand{\alert}[1]{\textbf{#1}}
>   [PACKAGES]
>
> --
> 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
From f21f04a8dace1e9d0cfcb9b1cf0d56f712381ba8 Mon Sep 17 00:00:00 2001
From: "Pedro A. Aranda" <[email protected]>
Date: Sun, 12 Apr 2026 12:28:37 +0200
Subject: [PATCH] ox-beamer: New LaTeX preamble sequence and new
 BEAMER_THEME_PRE

lisp/ox-latex.el: New slot :latex-class-post for the INFO
channel. Factor out the class template generation
to (org-latex--make-class-template).  Clean up and simplify the code.
lisp/ox-beamer.el: New option BEAMER_THEME_PRE.
(org-beamer-theme-settings): New function to manage theme-related
settings.
(org-beamer-template): Pass the generated Beamer class template using
the :latex-theme-post slot.
to (org-latex-make-preamble) through the INFO channel using the new .
testing/lisp/test-ox-beamer.el: New test for BEAMER_CLASS_PRE and the
new Latex class template.
doc/org-manual.org: Document BEAMER_THEME_PRE
etc/ORG-NEWS: Announce new LaTeX preamble sequence in Beamer header
and BEAMER_THEME_PRE.
---
 doc/org-manual.org             | 13 ++++++++
 etc/ORG-NEWS                   | 13 ++++++++
 lisp/ox-beamer.el              | 54 ++++++++++++++++++++------------
 lisp/ox-latex.el               | 57 ++++++++++++++++++++++++----------
 testing/lisp/test-ox-beamer.el | 16 ++++++++++
 5 files changed, 116 insertions(+), 37 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index baa1127a0..6658a7e51 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13162,6 +13162,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
+  =\usetheme{}=.  For example:
+
+  : #+BEAMER_THEME_PRE: \usepackage{geometry}
+  : #+BEAMER_THEME_PRE: \geometry{paperwidth=160mm, paperheight=90mm}
+  : #+BEAMER_THEME: Madrid
+
+  Use the ~Madrid~ theme, setting the page size to 160mm by 90mm. This
+  can be used to help creating figures with TiKZ.
+
 - =BEAMER_HEADER= ::

   #+cindex: @samp{BEAMER_HEADER}, keyword
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index c6063470b..3ff6260c3 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -42,6 +42,14 @@ 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
@@ -163,6 +171,11 @@ addition to being added to a data attribute, ~data-linenr~, on that
 highlight and copy/paste multiple lines from a code block without
 having the line numbers included.

+*** New option BEAMER_THEME_PRE
+
+The new option BEAMER_THEME_PRE is used to add arbitrary LaTeX lines
+that must be processed before the Beamer theme is loaded.
+
 ** New functions and changes in function arguments

 # This also includes changes in function behavior from Elisp perspective.
diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el
index 72fe18acd..cd1a28f85 100644
--- a/lisp/ox-beamer.el
+++ b/lisp/ox-beamer.el
@@ -261,6 +261,7 @@ Return overlay specification, as a string, or nil."
     (: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)
@@ -856,6 +857,33 @@ contextual information."

 ;;;; Template
 ;;
+;; Extract theme related information
+;;
+(defun org-beamer-theme-settings (info)
+  "Return the code related to the Beamer theme for the latex preamble.
+Will be called with the latex class is \"beamer\".
+INFO is a plist holding the export options."
+  (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"))
+	        ""))))
 ;; Template used is similar to the one used in `latex' backend,
 ;; excepted for the table of contents and Beamer themes.

@@ -865,37 +893,23 @@ 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)))
+    ;;
+    ;; We use the :latex-class-post to inject the theme settings as
+    ;; the first thingin the LaTeX preamble after the class declaration
+    ;;
+    (plist-put info :latex-class-post (org-beamer-theme-settings info))
     (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.
+     ;; Document class, theme and packages.
      (org-latex-make-preamble 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)
diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el
index f39240cd8..bd10d3644 100644
--- a/lisp/ox-latex.el
+++ b/lisp/ox-latex.el
@@ -125,6 +125,7 @@
     (:latex-header "LATEX_HEADER" nil nil newline)
     (:latex-header-extra "LATEX_HEADER_EXTRA" nil nil newline)
     (:latex-class-pre "LATEX_CLASS_PRE" nil nil newline)
+    (:latex-class-post nil nil nil)
     (:description "DESCRIPTION" nil nil parse)
     (:keywords "KEYWORDS" nil nil parse)
     (:subtitle "SUBTITLE" nil nil parse)
@@ -2032,6 +2033,40 @@ If the square brackets are missing, return STR enclosed in square brackets."
          (replace-regexp-in-string ; remove excess ] at the end
           "]+\\'" "]" str))))))

+;;; LaTeX class declaration
+(defun org-latex--make-class-template (info snippet?)
+  "Return the class template from INFO.
+Factored out from `org-latex-make-preamble' to improve readibility"
+  (let* ((class (plist-get info :latex-class))
+	 (class-options (org-latex--mk-options (plist-get info :latex-class-options)))
+	 (header (nth 1 (assoc class (plist-get info :latex-classes))))
+         (pre-template ;; this may need further ammending
+          (if (stringp header) ;; when the class is defined in org-latex-classes
+	      (mapconcat #'org-element-normalize-string
+		         (list
+                          (and (not snippet?)
+                               (plist-get info :latex-class-pre))
+		          (if (not class-options) header
+		            (replace-regexp-in-string
+		             "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)"
+		             class-options header t nil 1)))
+                         nil)
+           ;; unknown to org-latex-classes -> error
+            (user-error "Unknown LaTeX class `%s'" class))))
+    (mapconcat #'org-element-normalize-string
+               ;;
+               ;; Put all class related stuff here.
+               ;; ox-beamer and other classes should inject
+               ;; code that needs to go just after the class
+               ;; is defined using :latex-class-post
+               ;;
+	       (list (and (not snippet?)
+			  (plist-get info :latex-class-pre))
+                     pre-template
+                     (and (not snippet?)
+                          (plist-get info :latex-class-post)))
+               "")))
+
 ;;;###autoload
 (defun org-latex-make-preamble (info &optional template snippet?)
   "Return a formatted LaTeX preamble.
@@ -2041,22 +2076,11 @@ as expected by `org-splice-latex-header'.  When SNIPPET? is
 non-nil, only includes packages relevant to image generation, as
 specified in `org-latex-default-packages-alist' or
 `org-latex-packages-alist'."
-  (let* ((class (plist-get info :latex-class))
-	 (class-template
-	  (or template
-	      (let* ((class-options (org-latex--mk-options (plist-get info :latex-class-options)))
-		     (header (nth 1 (assoc class (plist-get info :latex-classes)))))
-		(and (stringp header)
-	             (mapconcat #'org-element-normalize-string
-		                (list
-                                 (and (not snippet?)
-                                      (plist-get info :latex-class-pre))
-		                 (if (not class-options) header
-		                   (replace-regexp-in-string
-			            "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)"
-			            class-options header t nil 1)))
-                                nil)))
-	      (user-error "Unknown LaTeX class `%s'" class))))
+  (let ((class-template
+         (or template
+             ;; Just in case someone needs to write
+             ;; (org-latex-make-preamble nil t)
+             (org-latex--make-class-template info snippet?))))
     (org-latex-guess-polyglossia-language
      (org-latex-guess-babel-language
       (org-latex-guess-inputenc
@@ -2073,7 +2097,6 @@ specified in `org-latex-default-packages-alist' or
                           (and (not snippet?)
                                (plist-get info :latex-use-sans)
                                "\\renewcommand*\\familydefault{\\sfdefault}"))
-
 		    ""))))
       info)
      info)))
diff --git a/testing/lisp/test-ox-beamer.el b/testing/lisp/test-ox-beamer.el
index 24550ec62..4c68fa33a 100644
--- a/testing/lisp/test-ox-beamer.el
+++ b/testing/lisp/test-ox-beamer.el
@@ -109,5 +109,21 @@ Here is a second example:
      (should (search-forward (concat "\\end{frame}") nil t))
      (should (search-forward (concat "\\end{" org-beamer-frame-environment "}"))))))

+(ert-deftest 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"))))
+
 (provide 'test-ox-beamer)
 ;;; test-ox-beamer.el ends here
--
2.43.0

Reply via email to