【分享】在 org-mode 和 latex-mode 中美化公式预览

可以修改 org-format-latex-header ,我改成了 standalone ,目前体验下来还行。

不,怎么能改那个模板呢…其它体验不要了嘛?

实际上应该是 org-latex-make-preamble 的 template 不应该在 xenops-math-latex-make-latex-document 里面指定。

另外这个问题就算是直接使用 xenops 编辑 latex 文件也不行。

能具体说说吗?目前我就了解到它和 latex preview 有关。

那是否可以用 advice 或 override 来支持自定义的模板呢?

能具体说说吗?目前我就了解到它和 latex preview 有关。

org latex babel

那是否可以用 advice 或 override 来支持自定义的模板呢?

那我猜你要 override 那一整个函数,而且依然不可靠。

xenops 本质也是使用 org 那一套,和修改 org-preview-latex-process-alist 后设置 org-preview-latex-default-process 没多少区别,反正现在 emacs 支持 svg(

而直接写 latex 大概又不需要在文档里面预览,有 auctex 的字体基本也够了…

1 个赞

这个是 org-mode 自带的 latex preview 效果,当然需要的话也可以 center

在这一部分中, 修改 :ascent 值在范围 90 ~ 100 中只能保证预览图片的底部与行内文本对齐: (:ascent 100, 效果).

这样对行内公式有上标的情况是友好的, 但倘若公式内有下标或者其他一些非文本高度的符号, 依然是不能保证文本与公式内文本对齐的, 见下图 (:ascent 100)

Screenshot 2022-10-16 at 10.18.54

因此我在思考能否有一种动态修改 :ascent 值的方法? 比方说公式内有上标时将其改为 90 ~ 100, 下标时 改为 0-10, 而其他情况则默认 :ascent center 这样. 但本人目前没有实现这个功能的思路.

发现有一个包 texfrag 基于 mathjax + dvipng 很好的实现了行内公式的对齐. 我尝试读了代码… 但本人水平很有限, 没有弄明白是如何实现的.

自带的不也是用的 org-format-latex-header 嘛,限制和 xenops 应该是一样的吧?

确实,反正都要自己写那一堆东西。其实最主要的吐槽是 xenops 不能在非 article 的 latex-mode 下用x

latex-mode 里面确实挺烦的,动不动就因为有些包没有而不能预览。

我的想法是这样的:

(defun eli/change-xenops-latex-header (orig &rest args)
  (let ( foo
          (org-format-latex-header "xxx"))
    (apply orig args)))
(advice-add 'xenops-math-latex-make-latex-document :around #'eli/change-xenops-latex-header)
(advice-add 'xenops-math-file-name-static-hash-data :around #'eli/change-xenops-latex-header)

自动根据当前项目设置 latex-header ,不知道会有什么问题。

自动根据当前项目设置 latex-header ,不知道会有什么问题。

对于 latex 来说还有很多呢。还是 beamer,xcolor 与 hyperref 需要作为 documentclass 的一部分传进去,\documentclass[xcolor={xxx},hyperref={xxx},…]{beamer},org mode 里面有 latex class options 所以没问题,然而 latex 不是 org,class 倒是 beamer 了,但是 header 部分就不是了,依然继承 org-mode 的 package header。

1 个赞

原来如此,感谢解惑

a

我使用xenops总是会遇到些怪问题,比如图中,在图片上按enter,效果并不是图片变回公式,而是变成了

  \begin{equation*}
    \mathcal H(\boldsymbol{p},\boldsymbol{q},t) = \sum^n_{i=1} p_i\dot q^i - \mathcal L(\boldsymbol{q},\boldsymbol{\dot q},t)

(注意到后面的\end{equation*}没有了,取而代之的是公式预览仍然在,并且与公式无关,所有的公式都这样)

并且只有从公式下方离开公式环境,才会让公式变成预览的样子;如果从上方离开,公式就不会生成预览。

强迫症实在是难受得不行

这是设计如此,因为 xenops 一开始是作者自用的。 见 xenops-math-render-below-maybe 的 docstring:

Render ELEMENT below ELEMENT, so that the rendering can be monitored while editing.

我不清楚你是讨厌仍然出现图片还是讨厌图片出现的位置,不过这两者都有办法解决,给 xenops-math-render-below-maybexenops-math-display-image 加个 advice 就好了。

我不清楚这是 bug 还是作者的考量,可能需要去问一下作者。不过源码里是这样的

(defun xenops-math-add-cursor-sensor-property ()
  (-when-let* ((element (xenops-math-parse-element-at-point)))
    (let ((beg (1+ (plist-get element :begin)))
          (end (plist-get element :end))
          (props '(cursor-sensor-functions (xenops-math-handle-element-transgression))))
      (add-text-properties beg end props)
      (add-text-properties (1- end) end '(rear-nonsticky (cursor-sensor-functions))))))

当然你可以修改 beg 的位置。

最后的效果是这样的:

Peek 2022-10-17 09-36

1 个赞

好像我们这里的效果还是不一样,我不修改任何配置的情况下,下方显示预览我认为是预期的行为,但是我的\end{equation*}不见了,因此比如我想更换environment的时候,把equation*换成equation就没法编辑了。而您这里则是把公式预览出现在了\end{equation}后。我认为最常用的场景,应该是在编辑公式时,将预览显示在\end{equation}的下方更好一些?

这是因为作者用图片 overlay 了这行。我个人也是比较偏好这种形式,因为这行基本上不需要手动去改

用 multiple cursor 之类的一起改 env 可能是更好的办法。

这当然可以,只是个人偏好,不过作者预览用的是 overlay ,你必须要额外处理 \end{equation} 的下方的文本和修改删除 overlay 的函数,所以比较麻烦。

感谢感谢,好像稍微明白一些了。请问如果我希望reveal的时候,就显示原本的公式latex代码,不要预览,就像一个异步的org-fragtog一样工作,该怎么做?

xenops-reveal-at-pointxenops-define-apply-at-point-command这个macro定义的,这里面又调用了xenops-apply-operations-at-point,再往下的嵌套实在是看不懂了。

(defun xenops-math-render-below-maybe (element)
  nil)

应该这样就可以了,没有其他测试,不确定是否有副作用

效果: Peek 2022-10-17 11-39

你可能还需要 (setq xenops-reveal-on-entry t)

1 个赞

加了xenops之后,我日常使用emacsclient总是加载不出来,但是直接从命令行启动emacs却没问题。

一顿排查之后,定位到一行(add-hook 'org-mode-hook #'xenops-mode),去掉这一行一切正常,自己进了org-mode之后也可以正常手动开启xenops-mode。百思不得其解。

后来发现,我设置了initial-major-modeorg-mode,去掉这个设置之后,就可以挂hook自动启用xenops-mode了。

另外,有两个踩的坑也做一个记录:

  • (setq xenops-reveal-on-entry t)之后的行为其实也和org-fragtog不太一致,我在试的时候发现无论怎么在预览上面上下移动,都不会进入公式编辑环境,一度以为是不是我用了iscroll-mode,previous-line next-line被iscroll版本替代了的原因。后来发现,所谓的“entry”,得是左右移动进入才能算。
  • 我自己上面提到的

借鉴了

我自己做了个advice的版本

  (defun xenops-math-reveal-alt (element)
    (xenops-element-overlays-delete element)
    (if current-prefix-arg
        (delete-file (xenops-math-get-cache-file element)))
    (let ((element-type (plist-get element :type))
          (begin-content (plist-get element :begin-content)))
      (goto-char (if (eq element-type 'block-math)
                     (1+ begin-content)
                   begin-content))))
  (advice-add #'xenops-math-reveal :override #'xenops-math-reveal-alt)

不过仍然有个从上方离开公式环境时预览显示不正确的问题,不过无伤大雅。

a

我倒是不太喜欢 org-fragtog 的方式,已经写好的公式大多数情况下不需要再修改了,这时候频繁地切换反而会影响编辑体验。

如何对齐 org-latex-preview 预览图的水平基准线?

按照当前思路用最简单的方式优化了基线校准, 最终效果如下:

与原本的效果对比:

旧:

新: Screenshot 2022-10-18 at 06.56.28

由于实现方式很简单, 只是单纯检查内容是否有上下标. 然后根据这些来单个调整图片位置.

所以目前还有许多不完善的地方, 希望能够慢慢修正.

代码如下:

(defun my/org-latex--get-tex-string ()
  "Return the content of the LaTeX fragment at point."
  (let ((datum (org-element-context)))
    (org-element-property :value datum)))

(defun my/latex-fragment-superscript-p ()
  "Return `t' if '^' in current LaTeX fragment."
  (memq 94 (string-to-list (my/org-latex--get-tex-string))))

(defun my/latex-fragment-subscript-p ()
  "Return `t' if '_' in current LaTeX fragment."
  (memq 95 (string-to-list (my/org-latex--get-tex-string))))

(defun my/latex-fragment-script-p ()
  "Return `t' if both '_' &  '^' in current LaTeX fragment."
  (and (memq 94 (string-to-list (my/org-latex--get-tex-string)))
       (memq 95 (string-to-list (my/org-latex--get-tex-string)))))

(defun org--make-preview-overlay (beg end image &optional imagetype)
  "Build an overlay between BEG and END using IMAGE file.
Argument IMAGETYPE is the extension of the displayed image,
as a string.  It defaults to \"png\"."
  (setq my/position 'center)
  (cond ((my/latex-fragment-script-p)
         (setq my/position 'center))
        ((my/latex-fragment-superscript-p)
         (setq my/position 100))
        ((my/latex-fragment-subscript-p)
         (setq my/position 70)))
  (let ((ov (make-overlay beg end))
	(imagetype (or (intern imagetype) 'png)))
    (overlay-put ov 'org-overlay-type 'org-latex-overlay)
    (overlay-put ov 'evaporate t)
    (overlay-put ov
		 'modification-hooks
		 (list (lambda (o _flag _beg _end &optional _l)
			 (delete-overlay o))))
    (overlay-put ov
		 'display
		 (list 'image :type imagetype :file image :ascent my/position))))

当然除此之外, 精准的基线校准方案依然是有的, 比如

(texfrag 我很难读懂它的代码, 没有明白它的实现方式.)

同时 dvisvgm 文档中所描述的解决方案: dvisvgm: Generating scalable vector graphics from DVI and EPS files, p363. 与 preview-latex 并调整 preview.el 的变量而可能实现的一种较为完美的解决方案. 但本人目前还没有找到具体实现方式.