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:
- png, to inline png 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
:package-version '(Org . "9.7")
:type 'plist)
@ -1738,6 +1740,7 @@ INFO is the current state of the export process, as a plist."
"Return close-tag for string TAG.
ATTR specifies additional attributes. INFO is a property list
containing current export state."
(declare (indent 1))
(concat "<" tag
(org-string-nw-p (concat " " attr))
(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)
"Make sure that appropriate preview images exist for all LaTeX.
TODO."
(when (assq (plist-get info :with-latex) org-latex-preview-process-alist)
(let* ((latex-preamble
(or org-latex-preview--preamble-content
(setq org-latex-preview--preamble-content
(org-latex-preview--get-preamble))))
(elements
(org-element-map parse-tree
'(latex-fragment latex-environment)
#'identity
info))
(entries-and-numbering
(org-latex-preview--construct-entries
elements t parse-tree))
(processing-type (plist-get info :with-latex))
(processing-info
(cdr (assq processing-type org-latex-preview-process-alist)))
(imagetype (or (plist-get processing-info :image-output-type) "png"))
(numbering-offsets (cons nil (cadr entries-and-numbering)))
(html-options (plist-get info :html-latex-image-options))
(element-hash-table (make-hash-table :test #'eq :size (length elements)))
fragment-info prev-fg prev-bg)
(cl-loop
for entry in (car entries-and-numbering)
for element in elements
do
(pcase-let* ((`(,beg ,end ,provided-value) entry)
(value (or provided-value
(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)))
Create a hash table containing preview images for all LaTeX
fragments in PARSE-TREE, and add it to INFO. Filter out
fragments to be ignored according to INFO (see the INFO argument
of `org-element-map').
The keys of the hash table are elements and the values are lists
containing image paths and metadata used for display."
(prog1 nil
(let ((processing-type (plist-get info :with-latex)))
(when (assq processing-type org-latex-preview-process-alist)
(let* ((image-options (plist-get info :html-latex-image-options))
(inline-condition (org-ensure-list
(plist-get image-options :inline)))
(image-type
(thread-first processing-type
(alist-get org-latex-preview-process-alist)
(plist-get :image-output-type)
(intern)))
(element-preview-hash-table
(apply #'org-latex-preview-cache-images parse-tree info
;; Do not copy preview images to :image-dir if
;; inlining of images in html is requested
(org-combine-plists
image-options
(and (memq image-type inline-condition)
(list :image-dir nil))))))
(plist-put info :html-latex-preview-hash-table element-preview-hash-table))))))
(defun org-html--as-latex (element info &optional content)
(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.
INFO is a plist holding contextual information, and it is assumed
that an image for ELEMENT already exists within it."
(let* ((hash (or (gethash element (plist-get info :html-latex-preview-hash-table))
(error "Expected LaTeX preview hash to exist for element, but none found")))
(path-info (or (org-latex-preview--get-cached hash)
(error "Expected LaTeX preview %S to exist in the cache" hash)))
(image-options (plist-get info :html-latex-image-options))
(let* ((path-info
(or (gethash element (plist-get info :html-latex-preview-hash-table))
(prog1 nil
(org-display-warning
(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) '(?$ ?\[)))
(image-source
(org-html-latex-image--data path-info hash info block-p)))
(unless (and (plist-get (cdr path-info) :height)
(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 (if path-info (org-html-latex-image--data path-info info block-p) "")))
(if (and (eq (plist-get (cdr path-info) :image-type) 'svg)
(memq 'svg-embed (plist-get image-options :inline)))
image-source
(let ((scaling (org-html-latex-image--scaling path-info info)))
(org-html-close-tag
"img"
(org-html--make-attribute-string
(list :src image-source
:alt (org-html-encode-plain-text
(org-element-property :value element))
:style (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)))
:class (format "org-latex org-latex-%s" (if block-p "block" "inline"))))
info)))))
(let ((scaling
(if (and (plist-get (cdr path-info) :height)
(plist-get (cdr path-info) :depth))
(org-html-latex-image--scaling path-info info)
(prog1 nil
(org-display-warning
(format "Missing geometry information for LaTeX preview image for element: %s"
(string-replace "\n" " " (org-element-property :value element))))))))
(org-html-close-tag "img"
(org-html--make-attribute-string
(nconc
(list :src image-source
: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)
"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))
: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.
This can take the form of a path, data URI, or <svg> element
depending on HASH and INFO. BLOCK-P signals that the image
should be a block element."
(let* ((image-options (plist-get info :html-latex-image-options))
(inline-condition (plist-get image-options :inline))
(image-dir (plist-get image-options :image-dir))
(inline-condition (org-ensure-list (plist-get image-options :inline)))
(image-format (plist-get (cdr image-path-info) :image-type))
(source-file (car image-path-info)))
(cond
((or inline-condition
(member (file-name-extension source-file)
(org-ensure-list inline-condition)))
(let ((coding-system-for-read 'utf-8)
(file-name-handler-alist nil))
(with-temp-buffer
(insert-file-contents-literally source-file)
(cond
((and (eq inline-condition 'svg-embed)
(eq image-format 'svg))
(goto-char (point-min))
(let ((svg-closing-tag (and (search-forward "<svg" nil t)
(search-forward ">" nil t))))
(if (memq image-format inline-condition)
(let ((coding-system-for-read 'utf-8)
(file-name-handler-alist nil))
(with-temp-buffer
(insert-file-contents-literally source-file)
(cond
((and (memq 'svg-embed inline-condition)
(eq image-format 'svg))
(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]+ -->"
" height=['\"][^\"']+[\"']"
" width=['\"][^\"']+[\"']"))
(goto-char (point-min))
(when (re-search-forward search svg-closing-tag t)
(replace-match "")))
(goto-char (point-min))
(when (re-search-forward "viewBox=['\"][^\"']+[\"']" svg-closing-tag t)
(insert
" style=\""
(let ((scaling (org-html-latex-image--scaling image-path-info info)))
(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))))
"\" class=\"org-latex org-latex-"
(if block-p "block" "inline")
"\"")))
(buffer-string))
((eq image-format 'svg)
;; Modelled after <https://codepen.io/tigt/post/optimizing-svgs-in-data-uris>.
(concat "data:image/svg+xml,"
(url-hexify-string
(subst-char-in-string ?\" ?\' (buffer-string))
'(?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
?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
?4 ?5 ?6 ?7 ?8 ?9 ?- ?_ ?. ?~
;;Special additions
?\s ?= ?: ?/))))
(t
(base64-encode-region (point-min) (point-max))
(goto-char (point-min))
(insert "data:image/" (symbol-name image-format) ";base64,")
(buffer-string))))))
((stringp image-dir)
(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))))
(dolist (search '("<!-- This file was generated by dvisvgm [^\n]+ -->"
" height=['\"][^\"']+[\"']"
" width=['\"][^\"']+[\"']"))
(goto-char (point-min))
(when (re-search-forward search svg-closing-tag t)
(replace-match "")))
(goto-char (point-min))
(when (re-search-forward "viewBox=['\"][^\"']+[\"']" svg-closing-tag t)
(insert
" style=\""
(let ((scaling (org-html-latex-image--scaling image-path-info info)))
(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))))
"\" class=\"org-latex org-latex-"
(if block-p "block" "inline")
"\"")))
(buffer-string))
((eq image-format 'svg)
;; Modelled after <https://codepen.io/tigt/post/optimizing-svgs-in-data-uris>.
(concat "data:image/svg+xml,"
(url-hexify-string
(subst-char-in-string ?\" ?\' (buffer-string))
'(?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
?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
?4 ?5 ?6 ?7 ?8 ?9 ?- ?_ ?. ?~
;;Special additions
?\s ?= ?: ?/))))
(t
(base64-encode-region (point-min) (point-max))
(goto-char (point-min))
(insert "data:image/" (symbol-name image-format) ";base64,")
(buffer-string)))))
source-file)))
;;;; Line Break