From 064e802169ce4ba99c019e908eb945f2a96274ba Mon Sep 17 00:00:00 2001 From: TEC Date: Mon, 6 Feb 2023 00:01:26 +0800 Subject: [PATCH 2/6] ox-latex: Apply new generated preamble to export * lisp/ox-latex.el (org-latex-template, org-latex-make-preamble, org-latex-guess-polyglossia-language, org-latex-guess-babel-language, org-latex-guess-inputenc, org-latex-generate-engraved-preamble): Refactor to make use of the generated export, and add a few new bells and whistles. * lisp/ox-beamer.el (org-beamer-template): Adjust to account for the changes in ox-latex.el. --- lisp/org.el | 15 -- lisp/ox-beamer.el | 10 +- lisp/ox-latex.el | 353 +++++++++++++++++++++++++++++----------------- 3 files changed, 223 insertions(+), 155 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 4d12084d9..4fb16d079 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -3385,14 +3385,6 @@ (defun org-get-packages-alist (var) (defcustom org-latex-default-packages-alist '(("AUTO" "inputenc" t ("pdflatex")) ("T1" "fontenc" t ("pdflatex")) - ("" "graphicx" t) - ("" "longtable" nil) - ("" "wrapfig" nil) - ("" "rotating" nil) - ("normalem" "ulem" t) - ("" "amsmath" t) - ("" "amssymb" t) - ("" "capt-of" nil) ("" "hyperref" nil)) "Alist of default packages to be inserted in the header. @@ -3403,15 +3395,8 @@ (defcustom org-latex-default-packages-alist Org mode to function properly: - inputenc, fontenc: for basic font and character selection -- graphicx: for including images -- longtable: For multipage tables - wrapfig: for figure placement - rotating: for sideways figures and tables -- ulem: for underline and strike-through -- amsmath: for subscript and superscript and math environments -- amssymb: for various symbols used for interpreting the entities - in `org-entities'. You can skip some of this package if you don't - use any of the symbols. - capt-of: for captions outside of floats - hyperref: for cross references diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el index 5df78d5a4..8924b412b 100644 --- a/lisp/ox-beamer.el +++ b/lisp/ox-beamer.el @@ -821,9 +821,7 @@ (defun org-beamer-template (contents info) ;; Time-stamp. (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, packages, and some configuration. (org-latex-make-preamble info) ;; Insert themes. (let ((format-theme @@ -872,12 +870,6 @@ (defun org-beamer-template (contents info) (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 org-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. diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index dc2062df5..9794e6ecd 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -127,6 +127,8 @@ (org-export-define-backend 'latex (:description "DESCRIPTION" nil nil parse) (:keywords "KEYWORDS" nil nil parse) (:subtitle "SUBTITLE" nil nil parse) + (:conditional-features nil nil org-latex-conditional-features) + (:feature-implementations nil nil org-latex-feature-implementations) ;; Other variables. (:latex-active-timestamp-format nil nil org-latex-active-timestamp-format) (:latex-caption-above nil nil org-latex-caption-above) @@ -1378,6 +1380,95 @@ (defun org-latex-generate-engraved-preamble (info) "% WARNING syntax highlighting unavailable as engrave-faces-latex was missing.\n") "\n"))) +;;;; Generated preamble + +(defcustom org-latex-conditional-features + `((t . !announce-start) + (t . !announce-end) + (t . !guess-pollyglossia) + (t . !guess-babel) + (t . !guess-inputenc) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + '(latex-fragment latex-environment) #'identity info t)) + . maths) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'underline #'identity info t)) + . underline) + ("\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\dotuline\\|\\markoverwith" + . underline) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'link + (lambda (link) + (and (member (org-element-property :type link) + '("http" "https" "ftp" "file")) + (file-name-extension (org-element-property :path link)) + (equal (downcase (file-name-extension + (org-element-property :path link))) + "svg"))) + info t)) + . svg) + (org-latex-tables-booktabs . booktabs) + (,(lambda (info) + (eq (plist-get info :latex-src-block-backend) 'engraved)) + . engraved-code) + ("^[ \t]*#\\+attr_latex: .*:float +wrap" + . float-wrap) + ("^[ \t]*#\\+attr_latex: .*:float +sideways" + . rotate) + ("^[ \t]*#\\+caption:\\|\\\\caption{" . caption)) + "A LaTeX-specific extension to `org-export-conditional-features', which see.") + +(defcustom org-latex-feature-implementations + `((!announce-start + :snippet ,(lambda (info) + (format "\n%%%% ox-latex features: %s" + (plist-get info :features))) + :order -100) + (maths :snippet "\\usepackage{amsmath}\n\\usepackage{amssymb}" :order 0.2) + (underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5) + (image :snippet "\\usepackage{graphicx}" :order 2) + (svg :snippet "\\usepackage[inkscapelatex=false]{svg}" :order 2 :when image) + (longtable :snippet "\\usepackage{longtable}" :when table :order 2) + (booktabs :snippet "\\usepackage{booktabs}" :when table :order 2) + (float-wrap :snippet "\\usepackage{wrapfig}" :order 2) + (rotate :snippet "\\usepackage{rotating}" :order 2) + (caption :snippet "\\usepackage{capt-of}") + (engraved-code :snippet org-latex-generate-engraved-preamble :when code) + (!guess-pollyglossia :snippet org-latex-guess-polyglossia-language) + (!guess-babel :snippet org-latex-guess-babel-language) + (!guess-inputenc :snippet org-latex-guess-inputenc) + (!announce-end :snippet "%% end ox-latex features\n" :order 100)) + "Alist describing how export features should be supported in the preamble. + +Implementation alist has the feature symbol as the car, with the +cdr forming a plist with the following keys: +- :snippet, which is either, + - A string, which should be included in the preamble verbatim. + - A variable, the value of which should be included in the preamble. + - A function, which is called with two arguments — the export info, + and the list of feature flags. The returned value is included in + the preamble. +- :requires, a feature or list of features which are needed. +- :when, a feature or list of features which imply this feature. +- :prevents, a feature or list of features that should be masked. +- :order, for when inclusion order matters. Feature implementations + with a lower order appear first. The default is 0." + :group 'org-export-general + :type '(plist :key-type + (choice (const :snippet) + (const :requires) + (const :when) + (const :prevents) + (const :order) + (const :trigger)) + :value-type + (choice (string :tag "Verbatim content") + (variable :tag "Content variable") + (function :tag "Generating function")))) + ;;;; Compilation (defcustom org-latex-compiler-file-string "%% Intended LaTeX compiler: %s\n" @@ -1620,29 +1711,29 @@ (defun org-latex--caption/label-string (element info) (org-trim label) (org-export-data main info)))))) -(defun org-latex-guess-inputenc (header) +(defun org-latex-guess-inputenc (info) "Set the coding system in inputenc to what the buffer is. -HEADER is the LaTeX header string. This function only applies -when specified inputenc option is \"AUTO\". +INFO is the plist used as a communication channel. +This function only applies when specified inputenc option is \"AUTO\". Return the new header, as a string." - (let* ((cs (or (ignore-errors - (latexenc-coding-system-to-inputenc - (or org-export-coding-system buffer-file-coding-system))) - "utf8"))) - (if (not cs) header + (let ((header (plist-get info :latex-full-header)) + (cs (or (ignore-errors + (latexenc-coding-system-to-inputenc + (or org-export-coding-system buffer-file-coding-system))) + "utf8"))) + (when (and cs (string-match "\\\\usepackage\\[\\(AUTO\\)\\]{inputenc}" header)) ;; First translate if that is requested. (setq cs (or (cdr (assoc cs org-latex-inputenc-alist)) cs)) - ;; Then find the \usepackage statement and replace the option. - (replace-regexp-in-string "\\\\usepackage\\[\\(AUTO\\)\\]{inputenc}" - cs header t nil 1)))) + (plist-put info :latex-full-header + (replace-match cs t t header 1)))) + nil) -(defun org-latex-guess-babel-language (header info) +(defun org-latex-guess-babel-language (info) "Set Babel's language according to LANGUAGE keyword. -HEADER is the LaTeX header string. INFO is the plist used as -a communication channel. +INFO is the plist used as a communication channel. Insertion of guessed language only happens when Babel package has explicitly been loaded. Then it is added to the rest of @@ -1656,50 +1747,46 @@ (defun org-latex-guess-babel-language (header info) Return the new header." (let* ((language-code (plist-get info :language)) - (plist (cdr - (assoc language-code org-latex-language-alist))) - (language (plist-get plist :babel)) - (language-ini-only (plist-get plist :babel-ini-only)) - ;; If no language is set, or Babel package is not loaded, or - ;; LANGUAGE keyword value is a language served by Babel - ;; exclusively through ini files, return HEADER as-is. - (header (if (or language-ini-only - (not (stringp language-code)) - (not (string-match "\\\\usepackage\\[\\(.*\\)\\]{babel}" header))) - header - (let ((options (save-match-data - (org-split-string (match-string 1 header) ",[ \t]*")))) - ;; If LANGUAGE is already loaded, return header - ;; without AUTO. Otherwise, replace AUTO with language or - ;; append language if AUTO is not present. Languages that are - ;; served in Babel exclusively through ini files are not added - ;; to the babel argument, and must be loaded using - ;; `\babelprovide'. - (replace-match - (mapconcat (lambda (option) (if (equal "AUTO" option) language option)) - (cond ((member language options) (delete "AUTO" options)) - ((member "AUTO" options) options) - (t (append options (list language)))) - ", ") - t nil header 1))))) + (plist (cdr (assoc language-code org-latex-language-alist))) + (language (plist-get plist :babel)) + (header (plist-get info :latex-full-header)) + (language-ini-only (plist-get plist :babel-ini-only)) + (babel-header-options + ;; If no language is set, or Babel package is not loaded, or + ;; LANGUAGE keyword value is a language served by Babel + ;; exclusively through ini files, return HEADER as-is. + (and (not language-ini-only) + (stringp language-code) + (string-match "\\\\usepackage\\[\\(.*\\)\\]{babel}" header) + (let ((options (save-match-data + (org-split-string (match-string 1 header) ",[ \t]*")))) + (cond ((member language options) (delete "AUTO" options)) + ((member "AUTO" options) options) + (t (append options (list language)))))))) + (when babel-header-options + ;; If AUTO is present in the header options, replace it with `language'. + (setq header + (replace-match + (mapconcat (lambda (option) (if (equal "AUTO" option) language option)) + babel-header-options + ", ") + t nil header 1))) ;; If `\babelprovide[args]{AUTO}' is present, AUTO is ;; replaced by LANGUAGE. - (if (not (string-match "\\\\babelprovide\\[.*\\]{\\(.+\\)}" header)) - header - (let ((prov (match-string 1 header))) - (if (equal "AUTO" prov) - (replace-regexp-in-string (format - "\\(\\\\babelprovide\\[.*\\]\\)\\({\\)%s}" prov) - (format "\\1\\2%s}" - (or language language-ini-only)) - header t) - header))))) - -(defun org-latex-guess-polyglossia-language (header info) + (when (string-match "\\\\babelprovide\\[.*\\]{AUTO}" header) + (setq header + (replace-regexp-in-string + (format + "\\(\\\\babelprovide\\[.*\\]\\)\\({\\)%s}" prov) + (format "\\1\\2%s}" (or language language-ini-only)) + header t))) + (plist-put info :latex-full-header header)) + nil) + +(defun org-latex-guess-polyglossia-language (info) "Set the Polyglossia language according to the LANGUAGE keyword. -HEADER is the LaTeX header string. INFO is the plist used as -a communication channel. +INFO is the plist used as a communication channel. Insertion of guessed language only happens when the Polyglossia package has been explicitly loaded. @@ -1710,48 +1797,50 @@ (defun org-latex-guess-polyglossia-language (header info) using \setdefaultlanguage and not as an option to the package. Return the new header." - (let* ((language (plist-get info :language))) + (let ((header (plist-get info :latex-full-header)) + (language (plist-get info :language))) ;; If no language is set or Polyglossia is not loaded, return ;; HEADER as-is. - (if (or (not (stringp language)) - (not (string-match - "\\\\usepackage\\(?:\\[\\([^]]+?\\)\\]\\){polyglossia}\n" - header))) - header + (when (and (stringp language) + (string-match + "\\\\usepackage\\(?:\\[\\([^]]+?\\)\\]\\){polyglossia}\n" + header)) (let* ((options (org-string-nw-p (match-string 1 header))) - (languages (and options - ;; Reverse as the last loaded language is - ;; the main language. - (nreverse - (delete-dups - (save-match-data - (org-split-string - (replace-regexp-in-string - "AUTO" language options t) - ",[ \t]*")))))) - (main-language-set - (string-match-p "\\\\setmainlanguage{.*?}" header))) - (replace-match - (concat "\\usepackage{polyglossia}\n" - (mapconcat - (lambda (l) - (let* ((plist (cdr - (assoc language org-latex-language-alist))) - (polyglossia-variant (plist-get plist :polyglossia-variant)) - (polyglossia-lang (plist-get plist :polyglossia)) - (l (if (equal l language) - polyglossia-lang - l))) - (format (if main-language-set (format "\\setotherlanguage{%s}\n" l) - (setq main-language-set t) - "\\setmainlanguage%s{%s}\n") - (if polyglossia-variant - (format "[variant=%s]" polyglossia-variant) - "") - l))) - languages - "")) - t t header 0))))) + (languages (and options + ;; Reverse as the last loaded language is + ;; the main language. + (nreverse + (delete-dups + (save-match-data + (org-split-string + (replace-regexp-in-string + "AUTO" language options t) + ",[ \t]*")))))) + (main-language-set + (string-match-p "\\\\setmainlanguage{.*?}" header)) + (polyglossia-modified-header + (replace-match + (concat "\\usepackage{polyglossia}\n" + (mapconcat + (lambda (l) + (let* ((plist (cdr (assoc language org-latex-language-alist))) + (polyglossia-variant (plist-get plist :polyglossia-variant)) + (polyglossia-lang (plist-get plist :polyglossia)) + (l (if (equal l language) + polyglossia-lang + l))) + (format (if main-language-set (format "\\setotherlanguage{%s}\n" l) + (setq main-language-set t) + "\\setmainlanguage%s{%s}\n") + (if polyglossia-variant + (format "[variant=%s]" polyglossia-variant) + "") + l))) + languages + "")) + t t header 0))) + (plist-put info :latex-full-header polyglossia-modified-header)))) + nil) (defun org-latex--remove-packages (pkg-alist info) "Remove packages based on the current LaTeX compiler. @@ -1952,32 +2041,45 @@ (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)) - (class-template - (or template - (let* ((class-options (plist-get info :latex-class-options)) - (header (nth 1 (assoc class (plist-get info :latex-classes))))) - (and (stringp header) - (if (not class-options) header - (replace-regexp-in-string - "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" - class-options header t nil 1)))) - (user-error "Unknown LaTeX class `%s'" class)))) - (org-latex-guess-polyglossia-language - (org-latex-guess-babel-language - (org-latex-guess-inputenc - (org-element-normalize-string - (org-splice-latex-header - class-template - (org-latex--remove-packages org-latex-default-packages-alist info) - (org-latex--remove-packages org-latex-packages-alist info) - snippet? - (mapconcat #'org-element-normalize-string - (list (plist-get info :latex-header) - (and (not snippet?) - (plist-get info :latex-header-extra))) - "")))) - info) - info))) + (class-template + (or template + (let* ((class-options (plist-get info :latex-class-options)) + (header (nth 1 (assoc class (plist-get info :latex-classes))))) + (and (stringp header) + (if (not class-options) header + (replace-regexp-in-string + "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" + class-options header t nil 1)))) + (user-error "Unknown LaTeX class `%s'" class))) + generated-preamble) + (plist-put info :latex-full-header + (org-element-normalize-string + (org-splice-latex-header + class-template + (org-latex--remove-packages org-latex-default-packages-alist info) + (org-latex--remove-packages org-latex-packages-alist info) + snippet? + (mapconcat #'org-element-normalize-string + (list (plist-get info :latex-header) + (and (not snippet?) + (plist-get info :latex-header-extra))) + "")))) + (setq generated-preamble + (if snippet? + (progn + (org-latex-guess-inputenc info) + (org-latex-guess-babel-language info) + (org-latex-guess-polyglossia-language info) + "\n% Generated preamble omitted for snippets.") + (org-export-generate-features-preamble info))) + (concat + ;; Time-stamp. + (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) + (plist-get info :latex-full-header) + generated-preamble))) (defun org-latex-template (contents info) "Return complete document string after LaTeX conversion. @@ -1986,12 +2088,7 @@ (defun org-latex-template (contents info) (let ((title (org-export-data (plist-get info :title) info)) (spec (org-latex--format-spec info))) (concat - ;; Time-stamp. - (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. + ;; Timestamp, compiler statement, document class and packages. (org-latex-make-preamble info) ;; Possibly limit depth for headline numbering. (let ((sec-num (plist-get info :section-numbers))) @@ -2028,12 +2125,6 @@ (defun org-latex-template (contents info) (let ((template (plist-get info :latex-hyperref-template))) (and (stringp template) (format-spec template spec))) - ;; engrave-faces-latex preamble - (when (and (eq org-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. -- 2.39.0