ox-html: Use org-latex-preview for LaTeX preview images

* lisp/ox-html.el (org-html-latex-image-options,
org-html-prepare-latex-images, org-html-latex-image,
org-html-latex-image--data, org-html-close-tag): Delegate LaTeX
preview image creation to `org-latex-preview-cache-images'.
Require both 'svg and 'svg-embed in the :inline condition of
`org-html-latex-image-options' to embed svg tags.  Handle LaTeX
preview image generation errors gracefully when exporting to html,
and highlight missing images in the html output.
This commit is contained in:
Karthik Chikmagalur 2024-04-06 13:23:31 -07:00 committed by TEC
parent bfad8754d7
commit 2143cf5f09
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
1 changed files with 117 additions and 148 deletions

View File

@ -1186,7 +1186,9 @@ This supports two extra properties,
inlined in the generated HTML. Valid format symbols are: inlined in the generated HTML. Valid format symbols are:
- png, to inline png images using <img> with a data URI - png, to inline png images using <img> with a data URI
- svg, to inline svg images using <img> with a data URI - svg, to inline svg images using <img> with a data URI
- svg-embed, to inline svg images using an <svg> element" - svg-embed, to inline svg images using an <svg> element.
This is only applied when used along with svg, as in
(svg svg-embed)."
:group 'org-export-html :group 'org-export-html
:package-version '(Org . "9.7") :package-version '(Org . "9.7")
:type 'plist) :type 'plist)
@ -1738,6 +1740,7 @@ INFO is the current state of the export process, as a plist."
"Return close-tag for string TAG. "Return close-tag for string TAG.
ATTR specifies additional attributes. INFO is a property list ATTR specifies additional attributes. INFO is a property list
containing current export state." containing current export state."
(declare (indent 1))
(concat "<" tag (concat "<" tag
(org-string-nw-p (concat " " attr)) (org-string-nw-p (concat " " attr))
(if (org-html-xhtml-p info) " />" ">"))) (if (org-html-xhtml-p info) " />" ">")))
@ -3051,64 +3054,34 @@ CONTENTS is nil. INFO is a plist holding contextual information."
(defun org-html-prepare-latex-images (parse-tree _backend info) (defun org-html-prepare-latex-images (parse-tree _backend info)
"Make sure that appropriate preview images exist for all LaTeX. "Make sure that appropriate preview images exist for all LaTeX.
TODO."
(when (assq (plist-get info :with-latex) org-latex-preview-process-alist) Create a hash table containing preview images for all LaTeX
(let* ((latex-preamble fragments in PARSE-TREE, and add it to INFO. Filter out
(or org-latex-preview--preamble-content fragments to be ignored according to INFO (see the INFO argument
(setq org-latex-preview--preamble-content of `org-element-map').
(org-latex-preview--get-preamble))))
(elements The keys of the hash table are elements and the values are lists
(org-element-map parse-tree containing image paths and metadata used for display."
'(latex-fragment latex-environment) (prog1 nil
#'identity (let ((processing-type (plist-get info :with-latex)))
info)) (when (assq processing-type org-latex-preview-process-alist)
(entries-and-numbering (let* ((image-options (plist-get info :html-latex-image-options))
(org-latex-preview--construct-entries (inline-condition (org-ensure-list
elements t parse-tree)) (plist-get image-options :inline)))
(processing-type (plist-get info :with-latex)) (image-type
(processing-info (thread-first processing-type
(cdr (assq processing-type org-latex-preview-process-alist))) (alist-get org-latex-preview-process-alist)
(imagetype (or (plist-get processing-info :image-output-type) "png")) (plist-get :image-output-type)
(numbering-offsets (cons nil (cadr entries-and-numbering))) (intern)))
(html-options (plist-get info :html-latex-image-options)) (element-preview-hash-table
(element-hash-table (make-hash-table :test #'eq :size (length elements))) (apply #'org-latex-preview-cache-images parse-tree info
fragment-info prev-fg prev-bg) ;; Do not copy preview images to :image-dir if
(cl-loop ;; inlining of images in html is requested
for entry in (car entries-and-numbering) (org-combine-plists
for element in elements image-options
do (and (memq image-type inline-condition)
(pcase-let* ((`(,beg ,end ,provided-value) entry) (list :image-dir nil))))))
(value (or provided-value (plist-put info :html-latex-preview-hash-table element-preview-hash-table))))))
(buffer-substring-no-properties beg end)))
(fg (plist-get html-options :foreground))
(bg (plist-get html-options :background))
(number (car (setq numbering-offsets (cdr numbering-offsets))))
(hash (org-latex-preview--hash
processing-type latex-preamble value imagetype fg bg number))
(options (org-combine-plists
org-latex-preview-appearance-options
html-options
(list :number number
:continue-color
(and (equal prev-bg bg)
(equal prev-fg fg))))))
(puthash element hash element-hash-table)
(unless (org-latex-preview--get-cached hash)
(push (list :string (org-latex-preview--tex-styled
processing-type value options)
:overlay (org-latex-preview--ensure-overlay beg end)
:key hash)
fragment-info))
(setq prev-fg fg prev-bg bg)))
(when fragment-info
(apply #'org-async-wait-for
(org-latex-preview--create-image-async
processing-type
(nreverse fragment-info)
:latex-preamble latex-preamble
:appearance-options html-options)))
(plist-put info :html-latex-preview-hash-table element-hash-table)
nil)))
(defun org-html--as-latex (element info &optional content) (defun org-html--as-latex (element info &optional content)
(let ((content (or content (org-element-property :value element)))) (let ((content (or content (org-element-property :value element))))
@ -3202,33 +3175,43 @@ CONTENTS is nil. INFO is a plist holding contextual information."
"Transcode the LaTeX fragment or environment ELEMENT from Org to HTML. "Transcode the LaTeX fragment or environment ELEMENT from Org to HTML.
INFO is a plist holding contextual information, and it is assumed INFO is a plist holding contextual information, and it is assumed
that an image for ELEMENT already exists within it." that an image for ELEMENT already exists within it."
(let* ((hash (or (gethash element (plist-get info :html-latex-preview-hash-table)) (let* ((path-info
(error "Expected LaTeX preview hash to exist for element, but none found"))) (or (gethash element (plist-get info :html-latex-preview-hash-table))
(path-info (or (org-latex-preview--get-cached hash) (prog1 nil
(error "Expected LaTeX preview %S to exist in the cache" hash))) (org-display-warning
(image-options (plist-get info :html-latex-image-options)) (format "Expected LaTeX preview image to exist for element, but none found: %s"
(string-replace "\n" " " (org-element-property :value element)))))))
(image-options (org-ensure-list (plist-get info :html-latex-image-options)))
(block-p (memq (aref (org-element-property :value element) 1) '(?$ ?\[))) (block-p (memq (aref (org-element-property :value element) 1) '(?$ ?\[)))
(image-source (image-source (if path-info (org-html-latex-image--data path-info info block-p) "")))
(org-html-latex-image--data path-info hash info block-p))) (if (and (eq (plist-get (cdr path-info) :image-type) 'svg)
(unless (and (plist-get (cdr path-info) :height) (memq 'svg-embed (plist-get image-options :inline)))
(plist-get (cdr path-info) :depth))
(error "Something went wrong during image generation"))
(if (and (eq (plist-get image-options :inline) 'svg-embed)
(eq (plist-get (cdr path-info) :image-type) 'svg))
image-source image-source
(let ((scaling (org-html-latex-image--scaling path-info info))) (let ((scaling
(org-html-close-tag (if (and (plist-get (cdr path-info) :height)
"img" (plist-get (cdr path-info) :depth))
(org-html--make-attribute-string (org-html-latex-image--scaling path-info info)
(list :src image-source (prog1 nil
:alt (org-html-encode-plain-text (org-display-warning
(org-element-property :value element)) (format "Missing geometry information for LaTeX preview image for element: %s"
:style (if block-p (string-replace "\n" " " (org-element-property :value element))))))))
(format "height: %.4fem; display: block" (plist-get scaling :height)) (org-html-close-tag "img"
(format "height: %.4fem; vertical-align: -%.4fem; display: inline-block" (org-html--make-attribute-string
(plist-get scaling :height) (plist-get scaling :depth))) (nconc
:class (format "org-latex org-latex-%s" (if block-p "block" "inline")))) (list :src image-source
info))))) :alt (org-html-encode-plain-text (org-element-property :value element))
:style
(if path-info
(if scaling
(if block-p
(format "height: %.4fem; display: block" (plist-get scaling :height))
(format "height: %.4fem; vertical-align: -%.4fem; display: inline-block"
(plist-get scaling :height) (plist-get scaling :depth)))
(if block-p "display: block" "display: inline-block"))
"color: red")
:class (format "org-latex org-latex-%s" (if block-p "block" "inline")))
(unless path-info (list :title "LaTeX preview image not generated."))))
info)))))
(defun org-html-latex-image--scaling (image-path-info info) (defun org-html-latex-image--scaling (image-path-info info)
"Determine the appropriate (<height> . <depth>) of IMAGE-PATH-INFO given INFO." "Determine the appropriate (<height> . <depth>) of IMAGE-PATH-INFO given INFO."
@ -3239,78 +3222,64 @@ that an image for ELEMENT already exists within it."
(list :height (* rescale-factor (plist-get (cdr image-path-info) :height)) (list :height (* rescale-factor (plist-get (cdr image-path-info) :height))
:depth (* rescale-factor (plist-get (cdr image-path-info) :depth))))) :depth (* rescale-factor (plist-get (cdr image-path-info) :depth)))))
(defun org-html-latex-image--data (image-path-info hash info &optional block-p) (defun org-html-latex-image--data (image-path-info info &optional block-p)
"Obtaine the image source for IMAGE-PATH-INFO as a string. "Obtaine the image source for IMAGE-PATH-INFO as a string.
This can take the form of a path, data URI, or <svg> element This can take the form of a path, data URI, or <svg> element
depending on HASH and INFO. BLOCK-P signals that the image depending on HASH and INFO. BLOCK-P signals that the image
should be a block element." should be a block element."
(let* ((image-options (plist-get info :html-latex-image-options)) (let* ((image-options (plist-get info :html-latex-image-options))
(inline-condition (plist-get image-options :inline)) (inline-condition (org-ensure-list (plist-get image-options :inline)))
(image-dir (plist-get image-options :image-dir))
(image-format (plist-get (cdr image-path-info) :image-type)) (image-format (plist-get (cdr image-path-info) :image-type))
(source-file (car image-path-info))) (source-file (car image-path-info)))
(cond (if (memq image-format inline-condition)
((or inline-condition (let ((coding-system-for-read 'utf-8)
(member (file-name-extension source-file) (file-name-handler-alist nil))
(org-ensure-list inline-condition))) (with-temp-buffer
(let ((coding-system-for-read 'utf-8) (insert-file-contents-literally source-file)
(file-name-handler-alist nil)) (cond
(with-temp-buffer ((and (memq 'svg-embed inline-condition)
(insert-file-contents-literally source-file) (eq image-format 'svg))
(cond (goto-char (point-min))
((and (eq inline-condition 'svg-embed) (let ((svg-closing-tag (and (search-forward "<svg" nil t)
(eq image-format 'svg)) (search-forward ">" nil t))))
(goto-char (point-min))
(let ((svg-closing-tag (and (search-forward "<svg" nil t)
(search-forward ">" nil t))))
(dolist (search '("<!-- This file was generated by dvisvgm [^\n]+ -->" (dolist (search '("<!-- This file was generated by dvisvgm [^\n]+ -->"
" height=['\"][^\"']+[\"']" " height=['\"][^\"']+[\"']"
" width=['\"][^\"']+[\"']")) " width=['\"][^\"']+[\"']"))
(goto-char (point-min)) (goto-char (point-min))
(when (re-search-forward search svg-closing-tag t) (when (re-search-forward search svg-closing-tag t)
(replace-match ""))) (replace-match "")))
(goto-char (point-min)) (goto-char (point-min))
(when (re-search-forward "viewBox=['\"][^\"']+[\"']" svg-closing-tag t) (when (re-search-forward "viewBox=['\"][^\"']+[\"']" svg-closing-tag t)
(insert (insert
" style=\"" " style=\""
(let ((scaling (org-html-latex-image--scaling image-path-info info))) (let ((scaling (org-html-latex-image--scaling image-path-info info)))
(if block-p (if block-p
(format "height: %.4fem; display: block" (plist-get scaling :height)) (format "height: %.4fem; display: block" (plist-get scaling :height))
(format "height: %.4fem; vertical-align: -%.4fem; display: inline-block" (format "height: %.4fem; vertical-align: -%.4fem; display: inline-block"
(plist-get scaling :height) (plist-get scaling :depth)))) (plist-get scaling :height) (plist-get scaling :depth))))
"\" class=\"org-latex org-latex-" "\" class=\"org-latex org-latex-"
(if block-p "block" "inline") (if block-p "block" "inline")
"\""))) "\"")))
(buffer-string)) (buffer-string))
((eq image-format 'svg) ((eq image-format 'svg)
;; Modelled after <https://codepen.io/tigt/post/optimizing-svgs-in-data-uris>. ;; Modelled after <https://codepen.io/tigt/post/optimizing-svgs-in-data-uris>.
(concat "data:image/svg+xml," (concat "data:image/svg+xml,"
(url-hexify-string (url-hexify-string
(subst-char-in-string ?\" ?\' (buffer-string)) (subst-char-in-string ?\" ?\' (buffer-string))
'(?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n '(?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n
?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z ?A ?B ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z ?A ?B
?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P
?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z ?0 ?1 ?2 ?3 ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z ?0 ?1 ?2 ?3
?4 ?5 ?6 ?7 ?8 ?9 ?- ?_ ?. ?~ ?4 ?5 ?6 ?7 ?8 ?9 ?- ?_ ?. ?~
;;Special additions ;;Special additions
?\s ?= ?: ?/)))) ?\s ?= ?: ?/))))
(t (t
(base64-encode-region (point-min) (point-max)) (base64-encode-region (point-min) (point-max))
(goto-char (point-min)) (goto-char (point-min))
(insert "data:image/" (symbol-name image-format) ";base64,") (insert "data:image/" (symbol-name image-format) ";base64,")
(buffer-string)))))) (buffer-string)))))
((stringp image-dir) source-file)))
(let* ((image-dir (expand-file-name image-dir))
(image-path (file-name-with-extension
(file-name-concat image-dir (substring hash 0 11))
(file-name-extension source-file))))
(unless (file-directory-p image-dir)
(mkdir image-dir t))
(unless (file-exists-p image-path)
(copy-file source-file image-path))
image-path))
(t source-file))))
;;;; Line Break ;;;; Line Break