org-latex-preview: Filter dvisvgm process for info

* lisp/org-latex-preview.el (org-latex-preview--display-info,
org-latex-preview--cleanup-callback, org-create-formula-image-async,
org--make-preview-overlay): Apply a filter to the dvisvgm process in
order to place overlays as images are produced, and along the way
extract size and baseline information that is used to more accurately
position the overlay image.  Because `org-latex-preview--place-images'
is now run within a filter and hence the stdout buffer, we need to
record the org buffer and switch to it before placing overlays.
(org-latex-preview--image-extract-async): Record the DPI-based scaling
factor so it can be accounted for when calculating the pt-scale fragment
height/width/depth.
(org-latex-format-options): Introduce a new parameter :zoom for
affecting the display scaling of images with associated height
resolution, but unlike :scale not the resolution/size of the images
files themselves.  This is then used in `org--make-preview-overlay'.
This commit is contained in:
TEC 2022-12-31 02:00:22 +08:00
parent cb225ab2f2
commit d528301806
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
1 changed files with 140 additions and 42 deletions

View File

@ -33,7 +33,8 @@
(defcustom org-format-latex-options (defcustom org-format-latex-options
'(:foreground default :background default :scale 1.0 '(:foreground default :background default :scale 1.0
:html-foreground "Black" :html-background "Transparent" :html-foreground "Black" :html-background "Transparent"
:html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) :html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")
:zoom 1.0)
"Options for creating images from LaTeX fragments. "Options for creating images from LaTeX fragments.
This is a property list with the following properties: This is a property list with the following properties:
:foreground the foreground color for images embedded in Emacs, e.g. \"Black\". :foreground the foreground color for images embedded in Emacs, e.g. \"Black\".
@ -52,7 +53,9 @@ This is a property list with the following properties:
\"$\" find math expressions surrounded by $...$ \"$\" find math expressions surrounded by $...$
\"$$\" find math expressions surrounded by $$....$$ \"$$\" find math expressions surrounded by $$....$$
\"\\(\" find math expressions surrounded by \\(...\\) \"\\(\" find math expressions surrounded by \\(...\\)
\"\\=\\[\" find math expressions surrounded by \\=\\[...\\]" \"\\=\\[\" find math expressions surrounded by \\=\\[...\\]
:zoom when the image has associated font-relative height information,
the display size is scaled by this factor."
:group 'org-latex :group 'org-latex
:type 'plist) :type 'plist)
@ -293,13 +296,19 @@ indeed LaTeX fragments/environments.")
If IMAGE file is specified, display it. Argument IMAGETYPE is the If IMAGE file is specified, display it. Argument IMAGETYPE is the
extension of the displayed image, as a string. It defaults to extension of the displayed image, as a string. It defaults to
\"png\"." \"png\"."
(let ((ov (make-overlay beg end)) (let* ((ov (make-overlay beg end))
(image-display (zoom (or (plist-get org-format-latex-options :zoom) 1.0))
(and path-info (height (plist-get (cdr path-info) :height))
(list 'image (depth (plist-get (cdr path-info) :depth))
:type (plist-get (cdr path-info) :image-type) (image-display
:file (car path-info) (and path-info
:ascent 'center)))) (list 'image
:type (plist-get (cdr path-info) :image-type)
:file (car path-info)
:height (and height (cons (* height zoom) 'em))
:ascent (if (and depth height)
(ceiling (* 100 (- 1.0 (/ depth height))))
'center)))))
(overlay-put ov 'org-overlay-type 'org-latex-overlay) (overlay-put ov 'org-overlay-type 'org-latex-overlay)
(overlay-put ov 'evaporate t) (overlay-put ov 'evaporate t)
(overlay-put ov 'modification-hooks (overlay-put ov 'modification-hooks
@ -601,17 +610,22 @@ during processing to hold more information on the fragments."
(let* ((extended-info (let* ((extended-info
(append processing-info (append processing-info
(list :fragments fragment-info (list :fragments fragment-info
:org-buffer (current-buffer)
:texfile (org-preview-latex--create-tex-file :texfile (org-preview-latex--create-tex-file
processing-info preview-strings)))) processing-info preview-strings))))
(tex-compile-async (tex-compile-async
(org-latex-preview--tex-compile-async extended-info)) (org-latex-preview--tex-compile-async extended-info))
(img-extract-async (img-extract-async
(org-latex-preview--image-extract-async extended-info))) (org-latex-preview--image-extract-async extended-info)))
(if (eq processing-type 'dvisvgm) (plist-put (cddr img-extract-async) :success
(plist-put (cddr img-extract-async) :success (list #'org-latex-preview--cleanup-callback))
#'org-latex-preview--dvisvgm-callback) (pcase processing-type
(plist-put (cddr img-extract-async) :success ('dvisvgm
#'org-latex-preview--cleanup-callback)) (plist-put (cddr img-extract-async) :filter
#'org-latex-preview--dvisvgm-filter))
(_
(plist-put (cddr img-extract-async) :success
(list #'org-latex-preview--generic-callback))))
(if (and (eq processing-type 'dvipng) (if (and (eq processing-type 'dvipng)
(member "--follow" (cadr img-extract-async))) (member "--follow" (cadr img-extract-async)))
(org-async-call img-extract-async) (org-async-call img-extract-async)
@ -729,6 +743,7 @@ The path of the created LaTeX file is returned."
(img-formatted-command (img-formatted-command
(split-string-shell-command (split-string-shell-command
(format-spec img-extract-command img-command-spec)))) (format-spec img-extract-command img-command-spec))))
(plist-put extended-info :dpi-scale-factor (/ dpi 140.0))
(list 'org-async-task (list 'org-async-task
img-formatted-command img-formatted-command
:buffer img-process-buffer :buffer img-process-buffer
@ -736,6 +751,24 @@ The path of the created LaTeX file is returned."
:failure "LaTeX preview image conversion failed! (error code %d)"))) :failure "LaTeX preview image conversion failed! (error code %d)")))
(defun org-latex-preview--cleanup-callback (_exit-code _stdout extended-info) (defun org-latex-preview--cleanup-callback (_exit-code _stdout extended-info)
"Delete files after image creation, in accord with EXTENDED-INFO."
(let* ((basename (file-name-sans-extension (plist-get extended-info :texfile)))
(images
(mapcar
(lambda (fragment-info)
(plist-get fragment-info :path))
(plist-get extended-info :fragments)))
(clean-exts
(or (plist-get extended-info :post-clean)
'(".dvi" ".xdv" ".pdf" ".tex" ".aux" ".log"
".svg" ".png" ".jpg" ".jpeg" ".out"))))
(dolist (img images)
(delete-file img))
(dolist (ext clean-exts)
(when (file-exists-p (concat basename ext))
(delete-file (concat basename ext))))))
(defun org-latex-preview--generic-callback (_exit-code _stdout extended-info)
"Move and delete files after image creation, in accords with EXTENDED-INFO." "Move and delete files after image creation, in accords with EXTENDED-INFO."
(let* ((basename (file-name-sans-extension (plist-get extended-info :texfile))) (let* ((basename (file-name-sans-extension (plist-get extended-info :texfile)))
(image-output-type (intern (plist-get extended-info :image-output-type))) (image-output-type (intern (plist-get extended-info :image-output-type)))
@ -751,15 +784,14 @@ The path of the created LaTeX file is returned."
(cl-loop (cl-loop
for fragment-info in (plist-get extended-info :fragments) for fragment-info in (plist-get extended-info :fragments)
for image-file in images for image-file in images
do for (beg . end) = (plist-get fragment-info :buffer-location)
(org-place-latex-image do (org-place-latex-image
(car (plist-get fragment-info :buffer-location)) beg end
(cdr (plist-get fragment-info :buffer-location)) (org-latex-preview--cache-image
(org-latex-preview--cache-image (plist-get fragment-info :key)
(plist-get fragment-info :key) image-file
image-file (org-latex-preview--display-info
(org-latex-preview--display-info extended-info fragment-info)))))
extended-info fragment-info)))))
(dolist (ext clean-exts) (dolist (ext clean-exts)
(when (file-exists-p (concat basename ext)) (when (file-exists-p (concat basename ext))
(delete-file (concat basename ext)))))) (delete-file (concat basename ext))))))
@ -767,27 +799,93 @@ The path of the created LaTeX file is returned."
(defun org-latex-preview--display-info (extended-info fragment-info) (defun org-latex-preview--display-info (extended-info fragment-info)
"From FRAGMENT-INFO and EXTENDED-INFO obtain display-relevant information." "From FRAGMENT-INFO and EXTENDED-INFO obtain display-relevant information."
(let ((image-type (intern (plist-get extended-info :image-output-type))) (let ((image-type (intern (plist-get extended-info :image-output-type)))
(fontsize (or (plist-get extended-info :fontsize) 10))
(dpi-factor (or (plist-get extended-info :dpi-scale-factor) 1.0))
info) info)
;; FUTURE extract width/height/etc. info (setq info (plist-put info :image-type image-type))
(plist-put info :image-type image-type))) (dolist (key '(:width :height :depth))
(when-let ((val (plist-get fragment-info key)))
(plist-put info key (/ val fontsize dpi-factor))))
info))
(defun org-latex-preview--dvisvgm-callback (_exit-code _stdout extended-info) (defun org-latex-preview--dvisvgm-filter (_proc _string extended-info)
"TODO" "Look for newly created images in the dvisvgm stdout buffer.
(let* ((basename (file-name-base (plist-get extended-info :texfile))) Any matches found will be matched against the fragments recorded in
(svg-images EXTENDED-INFO, and displayed in the buffer."
(directory-files (file-name-directory (plist-get extended-info :texfile)) (let ((dvisvgm-processing-re "^processing page \\([0-9]+\\)\n")
t (rx (literal basename) (* anything) ".svg")))) (dvisvgm-depth-re "depth=\\([0-9.]+\\)pt$")
(dolist (svg-file svg-images) (dvisvgm-size-re "^ *graphic size: \\([0-9.]+\\)pt x \\([0-9.]+\\)pt")
(with-temp-buffer (fragments (plist-get extended-info :fragments))
(insert-file-contents svg-file) page-marks fragments-to-show)
(goto-char (point-min)) (beginning-of-line)
(when (re-search-forward "<g fill='\\(#[0-9a-f]\\{6\\}\\)'" nil t) (save-excursion
(let* ((same-color (format "fill='\\(%s\\)'" (match-string 1)))) (while (re-search-forward dvisvgm-processing-re nil t)
(replace-match "currentColor" t t nil 1) (push (cons (string-to-number (match-string 1))
(while (re-search-forward same-color nil t) (match-beginning 0))
(replace-match "currentColor" t t nil 1))) page-marks)))
(write-region nil nil svg-file nil 0))))) (setq page-marks (nreverse page-marks))
(org-latex-preview--cleanup-callback nil nil extended-info)) (while page-marks
(let ((start (cdar page-marks))
(end (or (cdadr page-marks) (point-max)))
(page (caar page-marks))
fragment-info)
(goto-char start)
(when (save-excursion
(re-search-forward "output written to \\(.*.svg\\)$" end t))
(setq fragment-info (nth (1- page) fragments))
(plist-put fragment-info :path (expand-file-name (match-string 1) temporary-file-directory))
(when (save-excursion
(re-search-forward dvisvgm-depth-re end t))
(plist-put fragment-info :depth (string-to-number (match-string 1))))
(when (save-excursion (re-search-forward dvisvgm-size-re end t))
(plist-put fragment-info :height (string-to-number (match-string 2)))
(plist-put fragment-info :width (string-to-number (match-string 1))))
(when (save-excursion
(re-search-forward "^ page is empty" end t))
(unless (plist-get fragment-info :error)
(plist-put fragment-info :error "Image file not produced."))
(plist-put fragment-info :path nil))
(push fragment-info fragments-to-show)
(goto-char end)))
(setq page-marks (cdr page-marks)))
(when fragments-to-show
(setq fragments-to-show (nreverse fragments-to-show))
(mapc #'org-latex-preview--svg-make-fg-currentColor fragments-to-show)
(org-latex-preview--place-images extended-info fragments-to-show))))
(defun org-latex-preview--svg-make-fg-currentColor (svg-fragment)
"Replace the foreground color in SVG-FRAGMENT's file with \"currentColor\".
The foreground color is guessed to be the first specified <g>
fill color, which appears to be a reliable heuristic from a few
tests with the output of dvisvgm."
(with-temp-buffer
(insert-file-contents (plist-get svg-fragment :path))
(goto-char (point-min))
(when (re-search-forward "<g fill='\\(#[0-9a-f]\\{6\\}\\)'" nil t)
(let* ((same-color (format "\\(?:fill\\|stroke\\)='\\(%s\\)'" (match-string 1))))
(replace-match "currentColor" t t nil 1)
(while (re-search-forward same-color nil t)
(replace-match "currentColor" t t nil 1)))
(write-region nil nil (plist-get svg-fragment :path) nil 0))))
(defun org-latex-preview--place-images (extended-info &optional fragments)
"Place images for each of FRAGMENTS, according to their data and EXTENDED-INFO.
Should FRAGMENTS not be explicitly provided, all of the fragments
listed in EXTENDED-INFO will be used."
(let ((fragments (or fragments (plist-get extended-info :fragments))))
(with-current-buffer (plist-get extended-info :org-buffer)
(save-excursion
(cl-loop
for fragment-info in fragments
for image-file = (plist-get fragment-info :path)
for (beg . end) = (plist-get fragment-info :buffer-location)
do (org-place-latex-image
beg end
(org-latex-preview--cache-image
(plist-get fragment-info :key)
image-file
(org-latex-preview--display-info
extended-info fragment-info))))))))
(defun org-latex-preview--cache-image (key path info) (defun org-latex-preview--cache-image (key path info)
"Save the image at PATH with associated INFO in the cache indexed by KEY. "Save the image at PATH with associated INFO in the cache indexed by KEY.