latex公式渲染有兩種類型,一種是HTML形式展示公式,另一種是圖片形式展示公式。如果是HTML形式展示公式,渲染是在前端完成的,一般會比較緩慢。知乎采取的方式是以圖片形式展示公式。codecogs是一個latex公式渲染服務,它根據get請求返回一個svg圖片。例如:
- codecogs,這個服務的缺點是比較慢。
- 知乎的公式渲染:
https://www.zhihu.com/equation?tex=\frac{1}{3}
本文介紹ubuntu下搭建類似codecogs的公式渲染服務。
一、安裝latex
sudo apt-get install latex
sudo apt-get install latex-cjk-chinese
二、latex命令介紹
latex命令用於把tex文件轉換成pdf文件或者dvi文件。dvi是一種設備無關的可打印文件格式。
輸入dvi按兩次tab可以找到dvisvgm,此命令將dvi文件轉為svg。
三、編寫服務程序
使用flask編寫服務,通過命令行的方式調用latex獲取svg。在返回時需要注意兩點:
- 設置好content-type,否則客戶端不知道你返回的是什么格式的圖片
- 跨域訪問並不需要設置,因為加載的是靜態資源。跨域訪問只需要在header中設置:
"Access-Control-Allow-Origin": "*"
使用latex命令時需要注意:
- documentclass必須是minimal,這樣能夠保證生成的文件盡量小。
dvisvgm --no-fonts --no-styles,把dvi轉為svg時取消導出字體和格式,而只是簡單導出一張圖片,否則客戶端找不到這些字體和格式。- 使用
latex --interaction=nonstopmode,能夠保證即便報錯也不會阻塞 - 要引入amsmath,否則許多宏會找不到
- 為了防止用戶上傳不合法公式造成超時的現象,需要使用subprocess模塊,它是非阻塞的,父進程可以對子進程的運行時間進行監聽。
TODO:
- 添加緩存功能:對某個公式的請求可能很多,每次不需要調用latex重新生產,直接使用緩存結果。這個優化可能沒有必要,因為當查詢分布特別分散時,這個優化費力不討好。
- 添加統計功能:統計不同網站的請求次數,用來查看都有哪些人使用了本服務。其實調用別人服務是一件很危險的事情。調用別人的服務就是信任別人的服務,把別人的服務當做自己的一部分。當別人變得不可信任時,自己也就危險了。比如latex公式服務把返回的svg圖片統一替換成某個不合法的圖片。
import os
import signal
import subprocess
from flask import Flask, request, Response
app = Flask(__name__)
file_id = 0
latex_dir = os.path.join(os.path.expanduser("~"), "latex-server")
if not os.path.exists(latex_dir):
os.mkdir(latex_dir)
def run_command(s, log_file):
pro = subprocess.Popen(s, shell=True, preexec_fn=os.setsid)
try:
pro.wait(1) # 最多等待1秒鍾
except Exception as ex:
print(ex)
# pro.terminate()
os.killpg(os.getpgid(pro.pid), signal.SIGTERM) # 殺死一個進程組
raise ex
def gets(formula):
global file_id
file_id += 1
now = file_id
tex_file, dvi_file, svg_file, log_file = [os.path.join(latex_dir, "{}.{}".format(
now, file_type)) for file_type in "tex dvi svg log".split()]
open(tex_file, mode='w').write(r"""
\documentclass{minimal}
\usepackage{amsmath}
\begin{document}
$$%s$$
\end{document}
""" % formula)
try:
run_command("latex --interaction=nonstopmode --output-directory {} {}".format(
latex_dir, tex_file), log_file)
run_command(
"dvisvgm --no-fonts --no-styles -c2,2 -o {} {}".format(svg_file, dvi_file), log_file)
svg = open(svg_file).read() # 如果不存在,那就直接拋出異常吧
return svg
except Exception as ex:
raise ex
finally:
# 清理文件
for i in "tex dvi log aux svg".split():
filename = os.path.join(latex_dir, "{}.{}".format(now, i))
if os.path.exists(filename):
os.remove(filename)
@app.route("/render")
def render():
formula = request.args['formula']
try:
resp = gets(formula)
return Response(response=resp, headers={
"Content-Type": "image/svg+xml"
})
except Exception as ex:
print(ex)
return Response(status=500)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9988, debug=True)
