123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- from __future__ import annotations
- from contextlib import contextmanager
- import os
- import re
- import yaml
- 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_content_to_svg_file(
- content: str, template: str, additional_preamble: str,
- short_tex: 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"
- 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
- with display_during_execution("Writing " + short_tex):
- create_tex_svg(full_tex, svg_file, 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."
- )
- 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
- # TODO, perhaps this should live elsewhere
- @contextmanager
- def display_during_execution(message: str):
- # 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")
- class LatexError(Exception):
- pass
|