After checking the documentation, DocumentMetadata can be used with any
LaTeX compilers.

/PA
-- 
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 3dedfaf8481494beca9dc237c2624a2e1d8e76a9 Mon Sep 17 00:00:00 2001
From: "Pedro A. Aranda" <[email protected]>
Date: Sun, 28 Jun 2026 11:12:09 +0200
Subject: [PATCH] ox-latex.el: Add DocumentMetadata

* doc/org-manual.org: Document LATEX_METADATA keyword.
* etc/ORG-NEWS: Announce new LATEX_METADATA keyword.
* lisp/ox-latex.el : (org-latex--make-template-from-info) function.
Add LATEX_METADATA to info channel.
(org-latex-doc-metadata): New variable to hold the default document
metadata.
(org-latex-make-preamble): Check for valid class before
anything else.  Insert LATEX_METADATA before LATEX_CLASS_PRE.
* testing/lisp/test-ox-latex.el
(test-ox-latex/pdf-metadata):  Verify that the DocumentMetadata are
inserted; and that they inserted before LATEX_CLASS_PRE
---
 doc/org-manual.org            |  9 +++++++
 etc/ORG-NEWS                  |  5 ++++
 lisp/ox-latex.el              | 44 ++++++++++++++++++++++++++---------
 testing/lisp/test-ox-latex.el | 21 +++++++++++++++++
 4 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 9201a341a..d0efc26ab 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -14286,6 +14286,15 @@ general options (see [[*Export Settings]]).
   #+cindex: @samp{LATEX_CLASS_PRE}, keyword
   Arbitrary lines to prepend before the LaTeX preamble.
 
+- =LATEX_METADATA= ::
+
+  #+cindex: @samp{LATEX_METADATA}, keyword
+  #+vindex: org-latex-doc-metadata
+  Arbitrary lines with document metadata. When you set
+  ~org-latex-doc-metadata~ or you use ~#+LATEX_METADATA~, the
+  contents will be inserted into a LaTeX =\DocumentMetadata{}= command
+  that will be the very first LaTeX code in the preamble.
+
 - =KEYWORDS= ::
 
   #+cindex: @samp{KEYWORDS}, keyword
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 03057cbd0..f3358406f 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -144,6 +144,11 @@ A current limitation is that export blocks and keywords are only
 implemented for events and todos, and not yet for calendar-wide
 properties.
 
+*** New variable ~org-latex-doc-metadata~ and keyword ~#+LATEX_METADATA:~
+
+The LaTeX exporter can include PDF metadata with the new variable
+~org-latex-doc-metadata~ and the new keyword ~#+LATEX_METADATA:~.
+
 ** New and changed options
 
 # Changes dealing with changing default values of customizations,
diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el
index 8e2f72209..a8690a697 100644
--- a/lisp/ox-latex.el
+++ b/lisp/ox-latex.el
@@ -125,6 +125,7 @@ (org-export-define-backend 'latex
     (: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-metadata "LATEX_METADATA" nil org-latex-doc-metadata newline)
     (:description "DESCRIPTION" nil nil parse)
     (:keywords "KEYWORDS" nil nil parse)
     (:subtitle "SUBTITLE" nil nil parse)
@@ -652,6 +653,23 @@ (defcustom org-latex-hyperref-template
 		 (string :tag "Format string"))
   :safe #'string-or-null-p)
 
+(defcustom org-latex-doc-metadata nil
+  "A string with the document metadata.
+
+These metadata can be used to add additional information in the PDF
+to make it usable by onscreen readers and similar applications.
+
+This string will be inserted into a \"\\DocumentMetadata{}\" command
+at the beginning of the generated LaTeX.
+
+Alternatively you can use LATEX_CLASS_PRE, but you will have to write
+the complete LaTeX command as \"\\DocumentMetadata{...}\" yourself."
+  :version "31.1"
+  :package-version '(Org . "10")
+  :type '(choice (const :tag "No metadata" nil)
+		 (string :tag "Metadata for the PDF output"))
+  :safe #'string-or-null-p)
+
 ;;;; Headline
 
 (defcustom org-latex-format-headline-function
@@ -2061,21 +2079,25 @@ (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))
+         (doc-metadata (plist-get info :latex-metadata))
 	 (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))))
+                (unless (stringp header)
+	          (user-error "Unknown LaTeX class `%s'" class))
+	        (mapconcat #'org-element-normalize-string
+		           (list
+                            (and (not snippet?)
+                                 doc-metadata
+                                 (format "\\DocumentMetadata{%s}\n" doc-metadata))
+                            (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)))))
     (org-latex-guess-polyglossia-language
      (org-latex-guess-babel-language
       (org-latex-guess-inputenc
diff --git a/testing/lisp/test-ox-latex.el b/testing/lisp/test-ox-latex.el
index a804e281b..e2698783d 100644
--- a/testing/lisp/test-ox-latex.el
+++ b/testing/lisp/test-ox-latex.el
@@ -522,5 +522,26 @@ (ert-deftest test-ox-latex/subtree-export-with-language ()
        (should (search-forward "\\usepackage[utf8]{inputenc} \\usepackage[french, english]{babel}")))
      (kill-buffer export-buffer))))
 
+(ert-deftest test-ox-latex/pdf-metadata ()
+  "Test that DocumentMetadaData are inserted *before* LATEX_CLASS_PRE."
+  (org-test-with-exported-text
+   'latex
+   "#+TITLE: PDF Metadata
+#+LANGUAGE: en-gb es
+#+OPTIONS: toc:nil H:3 num:nil
+#+LATEX_COMPILER: pdflatex
+#+LATEX_METADATA: tagging = on
+#+LATEX_CLASS_PRE: \\PassOptionsToPackage{dvipsnames}{xcolor}
+#+LATEX_CLASS: report
+* Testing
+
+Just to see that DocumentMetadata comes before PassOptions and documentclass
+"
+   ;; (message "pdf-metadata: %s" (buffer-string))
+   (goto-char (point-min))
+   (should (search-forward "\\DocumentMetadata{tagging = on}" nil t))
+   (should (search-forward "\\PassOptionsToPackage{dvipsnames}{xcolor}" nil t))
+   (should (re-search-forward "^\\\\documentclass\\[.+?]{report}" nil t))))
+
 (provide 'test-ox-latex)
 ;;; test-ox-latex.el ends here
-- 
2.43.0

Reply via email to