【分享】在 org-mode 中使用 xenops 预览伪代码及 html 导出图片

最近需要在 org-mode 里写大量的伪代码,网上搜了下发现没有比较好的方案,于是自己折腾了下。

也欢迎大家分享自己的方案。

伪代码预览

先上效果图:

Peek 2023-01-08 11-56

基于 xenops 做了些扩展,使其能够解析相应的 LaTeX 代码块(我是用 algorithm2e 来写的)。

代码
(add-to-list 'xenops-elements '(algorithm
                                  (:delimiters
                                   ("^[ 	]*\\\\begin{algorithm}" "^[ 	]*\\\\end{algorithm}"))
                                  (:parser . xenops-math-parse-algorithm-at-point)
                                  (:handlers . block-math)))

  (defun xenops-math-parse-algorithm-at-point ()
    "Parse algorithm element at point."
    (xenops-parse-element-at-point 'algorithm))

  (defun xenops-math-parse-element-at-point ()
    "Parse any math element at point."
    (or (xenops-math-parse-inline-element-at-point)
        (xenops-math-parse-block-element-at-point)
        (xenops-math-parse-table-at-point)
        (xenops-math-parse-algorithm-at-point)))

  (defun xenops-math-block-delimiter-lines-regexp ()
    "A regexp matching the start or end line of any block math element."
    (format "\\(%s\\)"
            (s-join "\\|"
                    (apply #'append (xenops-elements-get-for-types '(block-math table algorithm) :delimiters)))))

HTML 导出时转换成图片

我比较喜欢在导出成 HTML 时将图片编码为 base64 ,这样就不用担心图床之类的问题了。

所以可以很方便的直接复用 xenops 的缓存图片,直接导出不需要其他 hack。

具体做法是使用 org-export-before-parsing-functionsorg-mode 9.5 及更低版本org-export-before-parsing-hook ),导出前将代码块换成相应的图片链接。

代码
(defun eli/filter-org-html--format-image (orig source attributes info)
    "Use base64 string instead of url to display images.

This functions is a advice for `org-html--format-image',
arguments, SOURCE ATTRIBUTES and INFO are like the arguments with
the same names of ORIG."
    (let ((image-html (funcall orig source attributes info))
          (image-base64 (format "data:image/%s+xml;base64,%s\"%s"
                                (or (file-name-extension source) "")
                                (base64-encode-string
                                 (with-temp-buffer
	                               (insert-file-contents-literally
                                    (file-relative-name
                                     (substring source 7)
                                     default-directory))
	                               (buffer-string)))
                                (file-name-nondirectory source))))
      (replace-regexp-in-string "img src=\"\\(.*?\\)\"" image-base64 image-html
                                nil nil 1)))
  (advice-add 'org-html--format-image :around #'eli/filter-org-html--format-image)

(defun eli/org-html-export-replace-algorithm-with-image (backend)
    "Replace algorithms in LaTeX with the corresponding images.

BACKEND is the back-end currently used, see `org-export-before-parsing-functions'"
    (when (org-export-derived-backend-p backend 'html)
      (let ((end (point-max)))
        (org-with-point-at (point-min)
          (let* ((case-fold-search t)
                 (algorithm-re "^[ 	]*\\\\begin{algorithm}"))
            (while (re-search-forward algorithm-re end t)
              (let* ((el (xenops-math-parse-algorithm-at-point))
                     (cache-file (abbreviate-file-name
                                  (xenops-math-get-cache-file el)))
                     (begin (plist-get el :begin))
                     (end (plist-get el :end)))
                (delete-region begin end)
                (insert (concat "[[" cache-file "]]")))))))))
  (add-to-list 'org-export-before-parsing-functions
               #'eli/org-html-export-replace-algorithm-with-image)

效果如下:

这样就可以比较舒服的在 org-mode 里写伪代码了,具体配置见:.elemacs/init-latex.el at master · Elilif/.elemacs · GitHub

2 个赞

请问展示 GIF 中 LaTeX 使用的字体是…?

用的 arev 宏包,字体应该是 Bitstream Vera Sans 的一个变种。

1 个赞

这个预览效果很不错呢。

导出 html 我使用的 GitHub - SaswatPadhi/pseudocode.js: Beautiful pseudocode for the Web + KaTeX 解析展示。

不确定是否支持 algorithm2e 大佬可以试试。

这个和 org-mode 结合的怎么样?方便分享下配置吗?

pseudocode 的渲染逻辑是:

for (item of document.getElementsByClassName('algorithm')) {pseudocode.renderElement(item, { lineNumber: true })}"

获取到某个 element 然后解析中间的 latex 代码。

例如:

  <div class="algorithm" id="org0edde1d">
\begin{algorithm}
  \caption{Bubble Sort}
  \begin{algorithmic}
    \INPUT An array A consisting of n elements
    \OUTPUT  A will be sorted in nondecreasing order
    \PROCEDURE{Sort}{A}
    \STATE $flag = true$
    \WHILE{$flag$}
    \STATE $flag=false$
    \FOR{$i=1$ \to $n$}
    \IF{$A[i]&gt;A[i+1]$}
    \STATE $flat=true$
    \STATE exchange $A[i]$ with $A[i+1]$
    \ENDIF
    \ENDFOR
    \ENDWHILE
    \ENDPROCEDURE
  \end{algorithmic}
\end{algorithm}
</div>

然后在 html 里会渲染这段 latex 代码为:

渲染的效果还不错。

为了生成一个 class 为 ‘algorithm’ div, 我在 org mode 里新增了一个 block begin_algorithm 不需要额外配置,org mode 导出时,begin_XX 会导出为 class 为 XX 的 div

#+begin_algorithm
\begin{algorithm}
  \caption{Bubble Sort}
  \begin{algorithmic}
...
  \end{algorithmic}
\end{algorithm}
#+end_algorithm

然后只需要把相关的 css 和 js 的依赖,添加到导出的 html 当中。

  <link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
  integrity="sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y"
  crossorigin="anonymous"
/>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css"
/>
<script
  defer="defer"
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"
  integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx"
  crossorigin="anonymous"
></script>

<script
  defer="defer"
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"
  integrity="sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe"
  crossorigin="anonymous"
  onload="renderMathInElement(document.body);"
></script>

<script
  defer="defer"
  src="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.js"
  onload="for (item of document.getElementsByClassName('algorithm')) {pseudocode.renderElement(item, { lineNumber: true })}"
></script>

优点是导出的 html 中算法部分不是图片,而是在页面用 js 渲染出来的。可以便捷的复制。

缺点是,需要用新的的 source block 包裹,以及不确定导出 pdf 之类的会不会有问题, 以及我没有试验是否支持复杂的语法。

刚刚看了下 readme ,确实不错。

可以原文中不包裹,方便预览,用 org-export-before-parsing-hook 在导出成 HTML 的时候再包裹。(初步想法

我去试试。

就是实际日常中,写伪代码的机会实在是不多。虽然伪代码看起来好看。但不如直接来个 python 代码来的方便。 :rofl: :rofl:

试了一下不支持,而且 algorithmic 也不是完全兼容,无法无缝地和 LaTeX 环境切换。

还是用回了图片的方案。

歪个楼。

我也一直使用 base64 在html中表示图片。感觉很合适,甚至可以进行一定的图片压缩减少html文件的大小。

但最近再尝试导出博客的 RSS。如果使用 base64 表示图片,导出的xml就不适合包含全文了,会让xml文件太大。

确实,我的 rss 里面正文就是很短的 preview。

xenops 是否能够显示公式中的中文?

\[
\sum\limits_{i=1}^n(\text{单项评分}_i * \text{权重})
\]