Org-mode 用 Typst 生成的 LaTeX 公式预览 SVG 很小

最近在尝试给 Org-mode 加一个用 Typst 做公式预览的功能,但是过程中遇到了一个问题,即生成的 SVG 显示出来的样子特别小: 图片

(上下为作为对比的普通文本大小)

emacs -Q 下也是如此。目前不太清楚这个问题要怎么排查解决,所以来请教坛友。

P.S. 关于这个使用 Typst 做公式预览的功能,思路上就是以 .typ 文件作为中间文件,用 Typst 生成 SVG。流程是先把 :latex-compiler 设为一个从 tex 生成 typ 文件的脚本,把 org-latex-preview 生成的 tex 文件预处理一下、去掉 hardcode 的 LaTeX headers,再加上 Typst 的 headers;然后用 Typst 从这个 .typ 文件生成 SVG。当然这么做有很多缺点,不过作为一个原型来说暂且可行。

实现如下:

(add-to-list 'org-preview-latex-process-alist
             '(typst :programs ("typst")
                     :description "Use Typst to generate svg"
                     :message "you need to install the programs: python and typst."
                     :image-input-type "typ"
                     :image-output-type "svg"
                     :image-size-adjust (1.0 . 1.0)
                     :post-clean
                     (".typ")
                     :latex-compiler
                     ("python ~/src/latex2typst.py %f %O")
                     :latex-header ""
                     :image-converter
                     ("typst compile %f %O")
                     ))

其中的 latex2typst.py 的内容如下:

import sys
import re

def extract_rgb_values(text):
    pattern = r'\\definecolor\{(fg|bg)\}\{rgb\}\{(.+?)\}'
    matches = re.findall(pattern, text)
    color_dict = {}
    for match in matches:
        color_name = match[0]
        color_values = match[1].split(',')
        color_values = [float(value) * 100 for value in color_values]
        color_dict[color_name] = color_values
    return (color_dict['fg'], color_dict['bg'])

if len(sys.argv) > 2:
    input_filename = sys.argv[1]
    output_filename = sys.argv[2]
else:
    print("Please specify input and ouput filenames")
    sys.exit(1)

with open(input_filename) as f:
    contents = f.read()

fg, bg = extract_rgb_values(contents)

index = contents.find('\\color{fg}')
formula = contents[index+len('\\color{fg}')+1:-19]
if formula.startswith('$$'):
    formula = formula[1:-1]

def typst_header(fg, bg):
    fg_str = ','.join([str(value) + "%" for value in fg])
    bg_str = ','.join([str(value) + "%" for value in bg])
    return f"""#set text(fill: rgb({fg_str}))
#set page(width: auto, height: auto, margin: 4pt, fill: rgb({bg_str}))
"""

if index != -1:
    with open(output_filename, 'w') as f:
        f.write(typst_header(fg, bg))
        f.write(formula)
5 个赞

调整 image-size-adjust 会有效果吗,或者考虑直接生成 png?

可以用 typst 里的 #scale 来放大

我用了另外一种解决方案,我希望混用 latex 和 typst 公式,就给 org-create-forula-image 加了一个 advice

默认情况下尝试用 typst 编译,如果有报错再退回到 latex

我这里设置的是 $ 包围的尝试用 typst 的数学模式编译; $$ 包围的用 typst 的 markup 模式编译

\( \[ \begin 这种包围的直接认为是 latex

(defvar org-typst-scale-ratio 180)
(defvar org-typst-debug nil
  "Enable debug messages for org Typst advice.")

(defun org-typst--debug (fmt &rest args)
  (when org-typst-debug
    (apply #'message (concat "[org-typst] " fmt) args)))

(defun org-create-formula-image--typst-advice (orig-fun string tofile options buffer &optional processing-type)
  "Advice for `org-create-formula-image' to add Typst support.

When PROCESSING-TYPE is 'latex and STRING is $ delimited, first try
compiling with Typst. If successful, return the SVG output directly.
Otherwise, fall back to the original LaTeX processing.

ORIG-FUN is the original function.
STRING, TOFILE, OPTIONS, BUFFER, and PROCESSING-TYPE are the original arguments."
  (org-typst--debug "Enter advice: processing-type=%S tofile=%S" processing-type tofile)
  (if (and (eq processing-type org-preview-latex-default-process)
           (numberp (string-match-p "\\`\\s-*\\$" string))
           (numberp (string-match-p "\\$\\s-*\\'" string)))
      (let* ((tmpdir temporary-file-directory)
             (typst-filebase (make-temp-name (expand-file-name "orgtypst" tmpdir)))
             (typst-file (concat typst-filebase ".typ"))
             (svg-file (concat typst-filebase ".svg"))
             (typst-content (replace-regexp-in-string "\\`\\s-*\\$\\s-*\\|\\s-*\\$\\s-*\\'" "" string))
             (normal-type (and (numberp (string-match-p "\\`\\s-*\\$" typst-content)) (numberp (string-match-p "\\$\\s-*\\'" typst-content))))
             (scale (plist-get options :scale))
             (fg-raw (plist-get options (if buffer :foreground :html-foreground)))
             (bg-raw (plist-get options (if buffer :background :html-background)))
             (fg (cond
                  ((null fg-raw) nil)
                  ((eq fg-raw 'default) nil)
                  ((stringp fg-raw) fg-raw)
                  (t nil)))
             (bg (cond
                  ((null bg-raw) nil)
                  ((eq bg-raw 'default) nil)
                  ((string= bg-raw "Transparent") "Transparent")
                  ((stringp bg-raw) bg-raw)
                  (t "Transparent"))))
        (org-typst--debug "Typst candidate: tmpdir=%S typst=%S svg=%S" tmpdir typst-file svg-file)
        (org-typst--debug "Scale=%S fg=%S bg=%S" scale fg bg)
        (condition-case err
            (progn
              (org-typst--debug "Writing Typst file...")
              (with-temp-file typst-file
                (let ((page-fill (cond
                                  ((or (null bg) (string= bg "Transparent")) "#none")
                                  (t (format "rgb(\"%s\")" bg)))))
                  (insert (format "#set page(width: auto, height: auto, margin: 0pt, fill: %s)\n" page-fill)))
                (when fg
                  (insert (format "#set text(fill: rgb(\"%s\"))\n" fg)))
                (let ((real-content (if normal-type
                                        (replace-regexp-in-string "\\`\\s-*\\$\\s-*\\|\\s-*\\$\\s-*\\'" "" typst-content)
                                      (format "$ %s $" typst-content))))
                  (insert (format "#scale(x:%f*%d%%,y:%f*%d%%,reflow:true)[\n %s \n]" scale org-typst-scale-ratio scale org-typst-scale-ratio real-content))))
              (org-typst--debug "Typst file written: %s (len=%d)" typst-file (nth 7 (file-attributes typst-file)))
              (with-current-buffer (get-buffer-create "*typst-formula*")
                (let ((inhibit-read-only t))
                  (erase-buffer)
                  (insert-file-contents typst-file)))
              (org-typst--debug "Running typst compile...")
              (let* ((compile-buf (get-buffer-create "*typst-compile*"))
                     (exit-code (call-process "typst" nil compile-buf nil
                                              "compile" typst-file svg-file)))
                (org-typst--debug "Typst exit-code=%s svg-exists=%s" exit-code (file-exists-p svg-file))
                (when org-typst-debug
                  (org-typst--debug "Typst output:\n%s"
                                    (with-current-buffer compile-buf
                                      (buffer-substring-no-properties (point-min) (point-max)))))
                (if (and (eq exit-code 0) (file-exists-p svg-file))
                    (progn
                      (org-typst--debug "Typst success; copying %s -> %s" svg-file tofile)
                      (copy-file svg-file tofile 'replace)
                      (when (file-exists-p typst-file) (delete-file typst-file))
                      (when (file-exists-p svg-file) (delete-file svg-file))
                      (org-typst--debug "Typst cleanup done; returning %s" tofile)
                      tofile)
                  (org-typst--debug "Typst failed; cleaning up and falling back to LaTeX")
                  (when (file-exists-p typst-file) (delete-file typst-file))
                  (when (file-exists-p svg-file) (delete-file svg-file))
                  (funcall orig-fun string tofile options buffer processing-type))))
          (error
           (org-typst--debug "Error: %s" (error-message-string err))
           (when (file-exists-p typst-file) (delete-file typst-file))
           (when (file-exists-p svg-file) (delete-file svg-file))
           (org-typst--debug "Fallback to LaTeX due to error")
           (funcall orig-fun string tofile options buffer processing-type))))
    (org-typst--debug "Not a Typst candidate; using original function")
    (funcall orig-fun string tofile options buffer processing-type)))

(after! org
  (advice-add 'org-create-formula-image :around #'org-create-formula-image--typst-advice))