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

161 lines
4.4 KiB
Python
Raw Normal View History

from __future__ import annotations
2022-05-22 23:47:45 +08:00
from contextlib import contextmanager
2022-04-12 19:19:59 +08:00
import os
import re
2022-05-22 23:47:45 +08:00
import yaml
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 = {}
def get_tex_font_preamble(tex_font: str) -> str:
2022-05-22 23:47:45 +08:00
name = re.sub(r"[^a-zA-Z]", "_", tex_font).lower()
with open(os.path.join(
get_manim_dir(), "manimlib", "tex_fonts.yml"
), encoding="utf-8") as tex_templates_file:
templates_dict = yaml.safe_load(tex_templates_file)
if name not in templates_dict:
log.warning(
"Cannot recognize font '%s', falling back to 'default'.",
name
)
name = "default"
result = templates_dict[name]
2022-05-28 22:44:30 +08:00
if name not in ("default", "ctex", "basic", "ctex_basic", "blank"):
result = templates_dict["basic"] + "\n" + result
2022-05-22 23:47:45 +08:00
return result
def get_tex_config() -> dict[str, str]:
"""
Returns a dict which should look something like this:
{
"compiler": "latex",
"font": "default",
"preamble": "..."
}
"""
2022-05-22 23:47:45 +08:00
# Only load once, then save thereafter
if not SAVED_TEX_CONFIG:
style_config = get_custom_config()["style"]
SAVED_TEX_CONFIG.update({
"compiler": style_config["tex_compiler"],
"font": style_config["tex_font"],
"preamble": get_tex_font_preamble(style_config["tex_font"])
})
2022-05-22 23:47:45 +08:00
return SAVED_TEX_CONFIG
def tex_content_to_svg_file(
content: str, tex_font: str, additional_preamble: str
) -> str:
tex_config = get_tex_config()
if not tex_font or tex_font == tex_config["font"]:
preamble = tex_config["preamble"]
2022-05-22 23:47:45 +08:00
else:
preamble = get_tex_font_preamble(tex_font)
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"
svg_file = os.path.join(
get_tex_dir(), hash_string(full_tex) + ".svg"
)
if not os.path.exists(svg_file):
# If svg doesn't exist, create it
create_tex_svg(full_tex, svg_file, tex_config["compiler"])
return svg_file
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."
)
with open(root + ".log", "r", encoding="utf-8") as log_file:
error_match_obj = re.search(r"(?<=\n! ).*", log_file.read())
if error_match_obj:
log.debug(
"The error could be: `%s`",
error_match_obj.group()
)
raise LatexError()
# 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
# TODO, perhaps this should live elsewhere
@contextmanager
def display_during_execution(message: str) -> None:
# Merge into a single line
to_print = message.replace("\n", " ")
max_characters = os.get_terminal_size().columns - 1
if len(to_print) > max_characters:
to_print = to_print[:max_characters - 3] + "..."
try:
print(to_print, end="\r")
yield
finally:
print(" " * len(to_print), end="\r")
2022-05-22 23:47:45 +08:00
class LatexError(Exception):
pass