2017-03-20 16:56:39 +01:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import subprocess
|
2018-05-10 19:13:36 +02:00
|
|
|
from typing import Optional
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2023-11-27 06:23:24 +01:00
|
|
|
import lxml.html
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.conf import settings
|
|
|
|
|
2019-07-17 02:29:08 +02:00
|
|
|
from zerver.lib.storage import static_path
|
2017-03-20 16:56:39 +01:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def render_tex(tex: str, is_inline: bool = True) -> Optional[str]:
|
2018-07-02 00:05:24 +02:00
|
|
|
r"""Render a TeX string into HTML using KaTeX
|
2017-03-20 16:56:39 +01:00
|
|
|
|
|
|
|
Returns the HTML string, or None if there was some error in the TeX syntax
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
tex -- Text string with the TeX to render
|
|
|
|
Don't include delimiters ('$$', '\[ \]', etc.)
|
|
|
|
is_inline -- Boolean setting that indicates whether the render should be
|
|
|
|
inline (i.e. for embedding it in text) or not. The latter
|
|
|
|
will show the content centered, and in the "expanded" form
|
|
|
|
(default True)
|
|
|
|
"""
|
|
|
|
|
2019-06-29 10:13:08 +02:00
|
|
|
katex_path = (
|
2019-07-17 02:29:08 +02:00
|
|
|
static_path("webpack-bundles/katex-cli.js")
|
2019-06-29 10:13:08 +02:00
|
|
|
if settings.PRODUCTION
|
|
|
|
else os.path.join(settings.DEPLOY_ROOT, "node_modules/katex/cli.js")
|
|
|
|
)
|
2017-03-20 16:56:39 +01:00
|
|
|
if not os.path.isfile(katex_path):
|
|
|
|
logging.error("Cannot find KaTeX for latex rendering!")
|
|
|
|
return None
|
2021-02-12 08:20:45 +01:00
|
|
|
command = ["node", katex_path]
|
2017-03-20 16:56:39 +01:00
|
|
|
if not is_inline:
|
2021-02-12 08:20:45 +01:00
|
|
|
command.extend(["--display-mode"])
|
2020-09-04 01:00:54 +02:00
|
|
|
try:
|
2022-01-22 07:52:54 +01:00
|
|
|
stdout = subprocess.check_output(command, input=tex, stderr=subprocess.DEVNULL, text=True)
|
2017-03-20 16:56:39 +01:00
|
|
|
# stdout contains a newline at the end
|
2020-09-04 01:00:54 +02:00
|
|
|
return stdout.strip()
|
|
|
|
except subprocess.CalledProcessError:
|
2017-03-20 16:56:39 +01:00
|
|
|
return None
|
2023-11-27 06:23:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
def change_katex_to_raw_latex(fragment: lxml.html.HtmlElement) -> None:
|
|
|
|
# Selecting the <span> elements with class 'katex'
|
|
|
|
katex_spans = fragment.xpath("//span[@class='katex']")
|
|
|
|
|
|
|
|
# Iterate through 'katex_spans' and replace with a new <span> having LaTeX text.
|
|
|
|
for katex_span in katex_spans:
|
|
|
|
latex_text = katex_span.xpath(".//annotation[@encoding='application/x-tex']")[0].text
|
|
|
|
# We store 'tail' to insert them back as the replace operation removes it.
|
|
|
|
tail = katex_span.tail
|
|
|
|
latex_span = lxml.html.Element("span")
|
|
|
|
latex_span.text = f"$${latex_text}$$"
|
|
|
|
katex_span.getparent().replace(katex_span, latex_span)
|
|
|
|
latex_span.tail = tail
|