接 【分享】在 org-mode 和 latex-mode 中美化公式预览 - #31,来自 Ilya.w 这里的讨论
参考了 auctex(主要是 preview.el)中计算 ascent 的方法,考虑使用 preview 宏包来实现基线对齐。但中间遇到了点小问题, preview 宏包使用 “fake error" 来传递目标数据:
这在 xenops 中被认为是编译错误而报错无法预览,所以必须修改 xenops-aio-subprocess 。
这样就能精准地对齐基准线了,最终效果:

代码如下:
(setq xenops-math-latex-process-alist
'((dvisvgm :programs
("latex" "dvisvgm")
:description "dvi > svg" :message "you need to install the programs: latex and dvisvgm." :image-input-type "dvi" :image-output-type "svg" :image-size-adjust
(1.7 . 1.5)
:latex-compiler
("latex -interaction nonstopmode -shell-escape -output-format dvi -output-directory %o \"\\nonstopmode\\nofiles\\PassOptionsToPackage{active,tightpage,auctex}{preview}\\AtBeginDocument{\\ifx\\ifPreview\\undefined\\RequirePackage[displaymath,floats,graphics,textmath,footnotes]{preview}[2004/11/05]\\fi}\\input\\detokenize{\"%f\"}\" %f")
:image-converter
("dvisvgm %f -n -b %B -c %S -o %O"))))
(defun xenops-aio-subprocess (command &optional _ __)
"Start asynchronous subprocess; return a promise.
COMMAND is the command to run as an asynchronous subprocess.
Resolve the promise when the process exits. The value function
does nothing if the exit is successful, but if the process exits
with an error status, then the value function signals the error."
(let* ((promise (aio-promise))
(name (format "xenops-aio-subprocess-%s"
(sha1 (prin1-to-string command))))
(output-buffer (generate-new-buffer name))
(sentinel
(lambda (process event)
(unless (process-live-p process)
(aio-resolve
promise
(lambda ()
(if (or (eq 0 (process-exit-status process))
(and (eq 1 (process-exit-status process))
(not (string-match-p
"^! [^P]"
(with-current-buffer output-buffer
(buffer-string))))))
(kill-buffer output-buffer)
(signal 'error
(prog1 (list :xenops-aio-subprocess-error-data
(list (s-join " " command)
event
(with-current-buffer output-buffer
(buffer-string))))
(kill-buffer output-buffer))))))))))
(prog1 promise
(make-process
:name name
:buffer output-buffer
:command command
:sentinel sentinel))))
(defun eli/xenops-preview-align-baseline (element &rest _args)
"Redisplay SVG image resulting from successful LaTeX compilation of ELEMENT.
Use the data in log file (e.g. \"! Preview: Snippet 1 ended.(368640+1505299x1347810).\")
to calculate the decent value of `:ascent'. "
(let* ((inline-p (eq 'inline-math (plist-get element :type)))
(ov-beg (plist-get element :begin))
(ov-end (plist-get element :end))
(colors (xenops-math-latex-get-colors))
(latex (buffer-substring-no-properties ov-beg
ov-end))
(cache-svg (xenops-math-compute-file-name latex colors))
(cache-log (file-name-with-extension cache-svg "log"))
(cache-log-exist-p (file-exists-p cache-log))
(tmp-log (f-join temporary-file-directory "xenops"
(concat (f-base cache-svg) ".log")))
(ov (car (overlays-at (/ (+ ov-beg ov-end) 2) t)))
(regexp-string "^! Preview:.*\(\\([0-9]*?\\)\\+\\([0-9]*?\\)x\\([0-9]*\\))")
img new-img ascent bbox log-text log)
(when (and ov inline-p)
(if cache-log-exist-p
(let ((text (f-read-text cache-log)))
(string-match regexp-string text)
(setq log (match-string 0 text))
(setq bbox (mapcar #'(lambda (x)
(* (preview-get-magnification)
(string-to-number x)))
(list
(match-string 1 text)
(match-string 2 text)
(match-string 3 text)))))
(with-temp-file cache-log
(insert-file-contents-literally tmp-log)
(goto-char (point-max))
(if (re-search-backward regexp-string nil t)
(progn
(setq log (match-string 0))
(setq bbox (mapcar #'(lambda (x)
(* (preview-get-magnification)
(string-to-number x)))
(list
(match-string 1)
(match-string 2)
(match-string 3))))))
(erase-buffer)
(insert log)))
(setq ascent (preview-ascent-from-bb (preview-TeX-bb bbox)))
(setq img (cdr (overlay-get ov 'display)))
(setq new-img (plist-put img :ascent ascent))
(overlay-put ov 'display (cons 'image new-img)))))
(advice-add 'xenops-math-display-image :after
#'eli/xenops-preview-align-baseline)
