3b1b-manim/manimlib/utils/tex_file_writing.py

150 lines
4 KiB
Python
Raw Normal View History

from __future__ import annotations
2022-04-12 19:19:59 +08:00
import os
import re
2022-05-22 23:47:45 +08:00
import yaml
from pathlib import Path
import tempfile
from manimlib.config import get_custom_config
2022-05-22 23:47:45 +08:00
from manimlib.config import get_manim_dir
2021-10-07 17:37:10 +08:00
from manimlib.logger import log
2022-04-12 19:19:59 +08:00
from manimlib.utils.directories import get_tex_dir
from manimlib.utils.simple_functions import hash_string
2018-05-09 14:10:04 -07:00
2022-05-22 23:47:45 +08:00
SAVED_TEX_CONFIG = {}
2022-08-22 21:52:48 +08:00
def get_tex_template_config(template_name: str) -> dict[str, str]:
2022-08-20 13:01:59 +08:00
name = template_name.replace(" ", "_").lower()
2022-05-22 23:47:45 +08:00
with open(os.path.join(
2022-05-28 23:18:56 +08:00
get_manim_dir(), "manimlib", "tex_templates.yml"
2022-05-22 23:47:45 +08:00
), encoding="utf-8") as tex_templates_file:
templates_dict = yaml.safe_load(tex_templates_file)
if name not in templates_dict:
log.warning(
2022-05-28 23:18:56 +08:00
"Cannot recognize template '%s', falling back to 'default'.",
2022-05-22 23:47:45 +08:00
name
)
name = "default"
2022-08-22 21:52:48 +08:00
return templates_dict[name]
2022-05-22 23:47:45 +08:00
def get_tex_config() -> dict[str, str]:
"""
Returns a dict which should look something like this:
{
2022-05-28 23:18:56 +08:00
"template": "default",
2022-08-22 21:52:48 +08:00
"compiler": "latex",
"preamble": "..."
}
"""
2022-05-22 23:47:45 +08:00
# Only load once, then save thereafter
if not SAVED_TEX_CONFIG:
2022-08-22 21:52:48 +08:00
template_name = get_custom_config()["style"]["tex_template"]
template_config = get_tex_template_config(template_name)
SAVED_TEX_CONFIG.update({
2022-08-22 21:52:48 +08:00
"template": template_name,
"compiler": template_config["compiler"],
"preamble": template_config["preamble"]
})
2022-05-22 23:47:45 +08:00
return SAVED_TEX_CONFIG
def tex_to_svg(
content: str,
template: str,
additional_preamble: str,
2022-05-22 23:47:45 +08:00
) -> str:
tex_config = get_tex_config()
2022-05-28 23:18:56 +08:00
if not template or template == tex_config["template"]:
2022-08-22 21:52:48 +08:00
compiler = tex_config["compiler"]
preamble = tex_config["preamble"]
2022-05-22 23:47:45 +08:00
else:
2022-08-22 21:52:48 +08:00
config = get_tex_template_config(template)
compiler = config["compiler"]
preamble = config["preamble"]
2022-05-22 23:47:45 +08:00
if additional_preamble:
preamble += "\n" + additional_preamble
full_tex = "\n\n".join((
"\\documentclass[preview]{standalone}",
preamble,
"\\begin{document}",
content,
"\\end{document}"
)) + "\n"
with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp:
create_tex_svg(full_tex, tmp.name, compiler)
# Read the contents
tmp.seek(0)
return tmp.read()
2022-05-22 23:47:45 +08:00
def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None:
if compiler == "latex":
program = "latex"
dvi_ext = ".dvi"
elif compiler == "xelatex":
program = "xelatex -no-pdf"
dvi_ext = ".xdv"
else:
raise NotImplementedError(
f"Compiler '{compiler}' is not implemented"
)
# Write tex file
root, _ = os.path.splitext(svg_file)
with open(root + ".tex", "w", encoding="utf-8") as tex_file:
tex_file.write(full_tex)
# tex to dvi
if os.system(" ".join((
program,
"-interaction=batchmode",
"-halt-on-error",
f"-output-directory=\"{os.path.dirname(svg_file)}\"",
f"\"{root}.tex\"",
">",
os.devnull
))):
log.error(
"LaTeX Error! Not a worry, it happens to the best of us."
)
error_str = ""
2022-05-22 23:47:45 +08:00
with open(root + ".log", "r", encoding="utf-8") as log_file:
error_match_obj = re.search(r"(?<=\n! ).*\n.*\n", log_file.read())
2022-05-22 23:47:45 +08:00
if error_match_obj:
error_str = error_match_obj.group()
2022-05-22 23:47:45 +08:00
log.debug(
f"The error could be:\n`{error_str}`",
2022-05-22 23:47:45 +08:00
)
raise LatexError(error_str)
2022-05-22 23:47:45 +08:00
# dvi to svg
os.system(" ".join((
"dvisvgm",
f"\"{root}{dvi_ext}\"",
"-n",
"-v",
"0",
"-o",
f"\"{svg_file}\"",
">",
os.devnull
)))
# Cleanup superfluous documents
for ext in (".tex", dvi_ext, ".log", ".aux"):
try:
os.remove(root + ext)
except FileNotFoundError:
pass
class LatexError(Exception):
pass