>From b66c291b1f0d1419742449bcde42bf0c4d620c23 Mon Sep 17 00:00:00 2001 From: TEC Date: Sun, 21 Nov 2021 20:04:12 +0800 Subject: [PATCH] ox-latex: Introduce "engraved" code highlighting * lisp/ox-latex.el (org-latex-src-block, org-latex-src-block--engraved, org-latex-inline-src-block, org-latex-inline-src-block--engraved, org-latex-template, org-latex-listings): Make use of the engraved-faces package (available on ELPA) to provide an alternative LaTeX code highlighting backend which functions similarly to htmlize.el for HTML exports. (org-latex-engraved-preamble, org-latex-engraved-options): Introduce variables to construct the preamble for engraved code blocks. * lisp/ox-beamer.el (org-beamer-template): Modify to add engrave-faces preamble when applicable. --- lisp/ox-beamer.el | 9 ++ lisp/ox-latex.el | 273 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 272 insertions(+), 10 deletions(-) diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el index 6be73c91e..73bd95539 100644 --- a/lisp/ox-beamer.el +++ b/lisp/ox-beamer.el @@ -857,6 +857,15 @@ (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 (eq org-latex-listings 'engraved) + (let ((src-p (org-element-map (plist-get info :parse-tree) + '(src-block inline-src-block) #'identity)) + (fixedw-p + (org-element-map (plist-get info :parse-tree) + '(example-block fixed-width) #'identity))) + (when (or src-p fixedw-p) + (org-latex-generate-engraved-preamble info src-p)))) ;; Document start. "\\begin{document}\n\n" ;; Title command. diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 4181db175..83bb6f078 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -37,6 +37,8 @@ (defvar org-latex-default-packages-alist) (defvar org-latex-packages-alist) (defvar orgtbl-exp-regexp) +(declare-function engrave-faces-latex-gen-preamble "ext:engrave-faces-latex") +(declare-function engrave-faces-latex-buffer "ext:engrave-faces-latex") ;;; Define Back-End @@ -125,6 +127,8 @@ (org-export-define-backend 'latex (:latex-default-quote-environment nil nil org-latex-default-quote-environment) (:latex-default-table-mode nil nil org-latex-default-table-mode) (:latex-diary-timestamp-format nil nil org-latex-diary-timestamp-format) + (:latex-engraved-options nil nil org-latex-engraved-options) + (:latex-engraved-preamble nil nil org-latex-engraved-preamble) (:latex-footnote-defined-format nil nil org-latex-footnote-defined-format) (:latex-footnote-separator nil nil org-latex-footnote-separator) (:latex-format-drawer-function nil nil org-latex-format-drawer-function) @@ -937,22 +941,48 @@ (defcustom org-latex-listings nil "Non-nil means export source code using the listings package. This package will fontify source code, possibly even with color. -If you want to use this, you also need to make LaTeX use the -listings package, and if you want to have color, the color -package. Just add these to `org-latex-packages-alist', for -example using customize, or with something like: +There are four implementations of this functionality you may +choose from (ordered from least to most capable): +1. Verbatim (nil) +2. Listings (t) +3. Minted (minted) +4. Engraved (engraved) + +The first two options provide basic syntax +highlighting (listings), or none at all (verbatim). + +When using listings, you also need to make use of the LaTeX +\"listings\" package. The \"color\" package is also needed if you +would like color too. These can simply be added to +`org-latex-packages-alist', using customise or something like: (require \\='ox-latex) (add-to-list \\='org-latex-packages-alist \\='(\"\" \"listings\")) (add-to-list \\='org-latex-packages-alist \\='(\"\" \"color\")) -Alternatively, +There are two options for more comprehensive fontification. The +first can be set with, + + (setq org-latex-listings \\='engraved) + +which causes source code to be run through +`engrave-faces-latex-buffer', which generates colorings using +Emacs' font-lock information. This requires the engrave-faces +package (availible from ELPA), and the fvextra LaTeX package be +installed. + +The styling of the engraved result can customised with +`org-latex-engraved-preamble' and `org-latex-engraved-options'. +The default preamble also uses the tcolorbox LaTeX package in +addition to fvextra. + +The second more comprehensive option can be used with, (setq org-latex-listings \\='minted) -causes source code to be exported using the minted package as -opposed to listings. If you want to use minted, you need to add -the minted package to `org-latex-packages-alist', for example +which causes source code to be exported using the minted package +as opposed to listings. If you want to use minted, you need to +add the minted package to `org-latex-packages-alist', for example using customize, or with (require \\='ox-latex) @@ -971,8 +1001,9 @@ (defcustom org-latex-listings nil :type '(choice (const :tag "Use listings" t) (const :tag "Use minted" minted) + (const :tag "Use engrave-faces-latex" engraved) (const :tag "Export verbatim" nil)) - :safe (lambda (s) (memq s '(t nil minted)))) + :safe (lambda (s) (memq s '(t nil minted engraved)))) (defcustom org-latex-listings-langs '((emacs-lisp "Lisp") (lisp "Lisp") (clojure "Lisp") @@ -1142,6 +1173,124 @@ (defcustom org-latex-custom-lang-environments nil :version "26.1" :package-version '(Org . "9.0")) +(defcustom org-latex-engraved-preamble + "\\usepackage{fvextra} + +[FVEXTRA-SETUP] + +\\renewcommand\\theFancyVerbLine{\\footnotesize\\color{black!40!white}\\arabic{FancyVerbLine}} + +\\usepackage{xcolor} + +\\providecolor{codebackground}{HTML}{f7f7f7} +\\providecolor{codeborder}{HTML}{f0f0f0} +\\providecolor{EFD}{HTML}{28292e} + +% TODO have code boxes keep line vertical alignment +\\usepackage[breakable,xparse]{tcolorbox} +\\DeclareTColorBox[]{Code}{o}% +{colback=codebackground, colframe=codeborder, + fontupper=\\footnotesize\\setlength{\\fboxsep}{0pt}, + colupper=EFD, + IfNoValueTF={#1}% + {boxsep=2pt, arc=2.5pt, outer arc=2.5pt, + boxrule=0.5pt, left=2pt}% + {boxsep=2.5pt, arc=0pt, outer arc=0pt, + boxrule=0pt, leftrule=1.5pt, left=0.5pt}, + right=2pt, top=1pt, bottom=0.5pt, + breakable}" + "Preamble content injected when using engrave-faces-latex for source blocks. +This is relevant when `org-latex-listings' is set to `engraved'. + +There is quite a lot of flexibility in what this preamble can be, as long as it: +- Loads the fvextra package. +- Loads the package xcolor (if it is not already loader elsewhere). +- Defines a \"Code\" environment (note the capital C), which can be + later used to wrap \"Verbatim\" environments (provided by fvextra). + +A macro-like placeholder is used to set fvextra's defaults according to +`org-latex-engraved-options': + + [FVEXTRA-SETUP] + +In the default value the color \"EFD\" is provided as this is the +foreground colour provided by engrave-faces-latex. When there +are example/fixed-width blocks only, the engraved generated +preamble is not included, and so it is provided so we may use it +anyway." + :group 'org-export-latex + :type 'string + :package-version '(Org . "9.6")) + +(defcustom org-latex-engraved-options + '(("commandchars" . "\\\\\\{\\}") + ("highlightcolor" . "white!95!black!80!blue") + ("breaklines" . "true") + ("breaksymbol" . "\\color{white!60!black}\\tiny\\ensuremath{\\hookrightarrow}")) + "Association list of options for the latex fvextra package when engraving code. + +These options are set using \\fvset{...} in the preamble of the +LaTeX export. Each element of the alist should be a list or cons +cell containing two strings: the name of the option, and the +value. For example, + + (setq org-latex-engraved-options + \\='((\"highlightcolor\" \"green\") (\"frame\" \"lines\"))) + ; or + (setq org-latex-engraved-options + \\='((\"highlightcolor\" . \"green\") (\"frame\" . \"lines\"))) + +will result in the following LaTeX in the preamble + +\\fvset{% + bgcolor=bg, + frame=lines} + +This will affect all fvextra environments. Note that the same +options will be applied to all blocks. If you need +block-specific options, you may use the following syntax: + + #+ATTR_LATEX: :options key1=value1,key2=value2 + #+BEGIN_SRC + ... + #+END_SRC" + :group 'org-export-latex + :type '(alist :key-type (string :tag "option") + :value-type (string :tag "value"))) + +(defun org-latex-generate-engraved-preamble (info syntax-colours-p) + "Generate the preamble to setup engraved code. +The result is constructed from `org-latex-engraved-preamble' and +`org-latex-engraved-options'." + (let* ((engraved-options + (plist-get info :latex-engraved-options)) + (engraved-preamble-template + (plist-get info :latex-engraved-preamble)) + (engraved-preamble + (if (string-match "^[ \t]*\\[FVEXTRA-SETUP\\][ \t]*\n?" + engraved-preamble-template) + (replace-match + (concat + "\\fvset{%\n " + (org-latex--make-option-string engraved-options ",\n ") + "}\n") + t t + engraved-preamble-template) + engraved-preamble-template))) + (if syntax-colours-p + (concat + "\n% Setup for code blocks [1/2]\n\n" + engraved-preamble + "\n\n% Setup for code blocks [2/2]: syntax highlighting colors\n" + (if (require 'engrave-faces-latex nil t) + (engrave-faces-latex-gen-preamble) + (message "Cannot engrave source blocks. Consider installing `engrave-faces'.") + "% WARNING syntax highlighting unavailible as engrave-faces-latex was missing.\n") + "\n") + (concat + "\n% Setup for code blocks\n\n" + engraved-preamble + "\n")))) ;;;; Compilation @@ -1756,6 +1905,15 @@ (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 (eq org-latex-listings 'engraved) + (let ((src-p (org-element-map (plist-get info :parse-tree) + '(src-block inline-src-block) #'identity)) + (fixedw-p + (org-element-map (plist-get info :parse-tree) + '(example-block fixed-width) #'identity))) + (when (or src-p fixedw-p) + (org-latex-generate-engraved-preamble info src-p)))) ;; Document start. "\\begin{document}\n\n" ;; Title command. @@ -2142,6 +2300,7 @@ (defun org-latex-inline-src-block (inline-src-block _contents info) (pcase (plist-get info :latex-listings) ('nil (org-latex--text-markup code 'code info)) ('minted (org-latex-inline-src-block--minted info code lang)) + ('engraved (org-latex-inline-src-block--engraved info code lang)) (_ (org-latex-inline-src-block--listings info code lang))))) (defun org-latex-inline-src-block--minted (info code lang) @@ -2149,7 +2308,7 @@ (defun org-latex-inline-src-block--minted (info code lang) INFO, CODE, and LANG are provided by `org-latex-inline-src-block'." (let ((mint-lang (or (cadr (assq (intern lang) (plist-get info :latex-minted-langs))) - (downcase org-lang))) + (downcase lang))) (options (org-latex--make-option-string (plist-get info :latex-minted-options)))) (format "\\mintinline%s{%s}{%s}" @@ -2157,6 +2316,24 @@ (defun org-latex-inline-src-block--minted (info code lang) mint-lang code))) +(defun org-latex-inline-src-block--engraved (_info code lang) + "Transcode an inline src block's content from Org to LaTeX, using engrave-faces. +INFO, CODE, and LANG are provided by `org-latex-inline-src-block'." + (if (require 'engrave-faces-latex nil t) + (let (engraved-buffer engraved-code) + (setq engraved-buffer + (with-temp-buffer + (insert code) + (funcall (org-src-get-lang-mode lang)) + (engrave-faces-latex-buffer))) + (setq engraved-code + (with-current-buffer engraved-buffer + (buffer-string))) + (kill-buffer engraved-buffer) + (format "\\Verb{%s}" + engraved-code)) + (user-error "Cannot engrave inline src block, `engrave-faces-latex' is unavailible."))) + (defun org-latex-inline-src-block--listings (info code lang) "Transcode an inline src block's content from Org to LaTeX, using lstlistings. INFO, CODE, and LANG are provided by `org-latex-inline-src-block'." @@ -3017,6 +3194,9 @@ (defun org-latex-src-block (src-block _contents info) num-start retain-labels attributes float custom-env)) ((eq listings 'minted) (org-latex-src-block--minted src-block info lang caption caption-above-p label + num-start retain-labels attributes float)) + ((eq listings 'engraved) + (org-latex-src-block--engraved src-block info lang caption caption-above-p label num-start retain-labels attributes float)) (t (org-latex-src-block--listings src-block info lang caption caption-above-p label @@ -3133,6 +3313,79 @@ (defun org-latex-src-block--minted ;; Return value. (format float-env body))) +(defun org-latex-src-block--engraved + (src-block info lang caption caption-above-p _label + num-start retain-labels attributes float) + "Transcode a SRC-BLOCK element from Org to LaTeX, using engrave-faces-latex. +LANG, CAPTION, CAPTION-ABOVE-P, LABEL, NUM-START, RETAIN-LABELS, ATTRIBUTES +and FLOAT are extracted from SRC-BLOCK and INFO in `org-latex-src-block'." + (if (require 'engrave-faces-latex nil t) + (let* ((caption-str (org-latex--caption/label-string src-block info)) + (placement (or (org-unbracket-string "[" "]" (plist-get attributes :placement)) + (plist-get info :latex-default-figure-position))) + (float-env + (cond + ((string= "multicolumn" float) + (format "\\begin{listing*}[%s]\n%s%%s\n%s\\end{listing*}" + placement + (if caption-above-p caption-str "") + (if caption-above-p "" caption-str))) + (caption + (format "\\begin{listing}[%s]\n%s%%s\n%s\\end{listing}" + placement + (if caption-above-p caption-str "") + (if caption-above-p "" caption-str))) + ((string= "t" float) + (concat (format "\\begin{listing}[%s]\n" + placement) + "%s\n\\end{listing}")) + (t "%s"))) + (options (plist-get info :latex-engraved-options)) + (content-buffer + (with-temp-buffer + (insert + (let* ((code-info (org-export-unravel-code src-block)) + (max-width + (apply 'max + (mapcar 'length + (org-split-string (car code-info) + "\n"))))) + (org-export-format-code + (car code-info) + (lambda (loc _num ref) + (concat + loc + (when ref + ;; Ensure references are flushed to the right, + ;; separated with 6 spaces from the widest line + ;; of code. + (concat (make-string (+ (- max-width (length loc)) 6) + ?\s) + (format "(%s)" ref))))) + nil (and retain-labels (cdr code-info))))) + (funcall (org-src-get-lang-mode lang)) + (engrave-faces-latex-buffer))) + (content + (with-current-buffer content-buffer + (buffer-string))) + (body + (format + "\\begin{Code}\n\\begin{Verbatim}[%s]\n%s\\end{Verbatim}\n\\end{Code}" + ;; Options. + (concat + (org-latex--make-option-string + (append + (when (and num-start (not (assoc "linenos" options))) + `(("linenos") + ("firstnumber" ,(number-to-string (1+ num-start))))) + (let ((local-options (plist-get attributes :options))) + (and local-options (list local-options)))))) + content))) + (kill-buffer content-buffer) + ;; Return value. + (format float-env body)) + (user-error "Cannot engrave src block, `engrave-faces-latex' is unavailible."))) + (defun org-latex-src-block--listings (src-block info lang caption caption-above-p label num-start retain-labels attributes float) -- 2.35.3