from __future__ import annotations import os import re import yaml from pathlib import Path import tempfile from manimlib.config import get_custom_config from manimlib.config import get_manim_dir from manimlib.logger import log from manimlib.utils.directories import get_tex_dir from manimlib.utils.simple_functions import hash_string SAVED_TEX_CONFIG = {} def get_tex_template_config(template_name: str) -> dict[str, str]: name = template_name.replace(" ", "_").lower() with open(os.path.join( get_manim_dir(), "manimlib", "tex_templates.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 template '%s', falling back to 'default'.", name ) name = "default" return templates_dict[name] def get_tex_config() -> dict[str, str]: """ Returns a dict which should look something like this: { "template": "default", "compiler": "latex", "preamble": "..." } """ # Only load once, then save thereafter if not SAVED_TEX_CONFIG: template_name = get_custom_config()["style"]["tex_template"] template_config = get_tex_template_config(template_name) SAVED_TEX_CONFIG.update({ "template": template_name, "compiler": template_config["compiler"], "preamble": template_config["preamble"] }) return SAVED_TEX_CONFIG def tex_to_svg( content: str, template: str, additional_preamble: str, ) -> str: tex_config = get_tex_config() if not template or template == tex_config["template"]: compiler = tex_config["compiler"] preamble = tex_config["preamble"] else: config = get_tex_template_config(template) compiler = config["compiler"] preamble = config["preamble"] 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() 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 = "" with open(root + ".log", "r", encoding="utf-8") as log_file: error_match_obj = re.search(r"(?<=\n! ).*\n.*\n", log_file.read()) if error_match_obj: error_str = error_match_obj.group() log.debug( f"The error could be:\n`{error_str}`", ) raise LatexError(error_str) # 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