mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00

* Remove print("Reloading...") * Change where exception mode is set, to be quieter * Add default fallback monitor for when no monitors are detected * Have StringMobject work with svg strings rather than necessarily writing to file Change SVGMobject to allow taking in a string of svg code as an input * Add caching functionality, and have Tex and Text both use it for saved svg strings * Clean up tex_file_writing * Get rid of get_tex_dir and get_text_dir * Allow for a configurable cache location * Make caching on disk a decorator, and update implementations for Tex and Text mobjects * Remove stray prints * Clean up how configuration is handled In principle, all we need here is that manim looks to the default_config.yaml file, and updates it based on any local configuration files, whether in the current working directory or as specified by a CLI argument. * Make the default size for hash_string an option * Remove utils/customization.py * Remove stray prints * Consolidate camera configuration This is still not optimal, but at least makes clearer the way that importing from constants.py kicks off some of the configuration code. * Factor out configuration to be passed into a scene vs. that used to run a scene * Use newer extract_scene.main interface * Add clarifying message to note what exactly is being reloaded * Minor clean up * Minor clean up * If it's worth caching to disk, then might as well do so in memory too during development * No longer any need for custom hash_seeds in Tex and Text * Remove display_during_execution * Get rid of (no longer used) mobject_data directory reference * Remove get_downloads_dir reference from register_font * Update where downloads go * Easier use of subdirectories in configuration * Add new pip requirements
172 lines
5 KiB
Python
172 lines
5 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import yaml
|
|
import subprocess
|
|
from functools import lru_cache
|
|
|
|
from pathlib import Path
|
|
import tempfile
|
|
|
|
from manimlib.utils.cache import cache_on_disk
|
|
from manimlib.config import get_global_config
|
|
from manimlib.config import get_manim_dir
|
|
from manimlib.logger import log
|
|
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()
|
|
template_path = os.path.join(get_manim_dir(), "manimlib", "tex_templates.yml")
|
|
with open(template_path, 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_global_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 get_full_tex(content: str, preamble: str = ""):
|
|
return "\n\n".join((
|
|
"\\documentclass[preview]{standalone}",
|
|
preamble,
|
|
"\\begin{document}",
|
|
content,
|
|
"\\end{document}"
|
|
)) + "\n"
|
|
|
|
|
|
@lru_cache(maxsize=128)
|
|
@cache_on_disk
|
|
def latex_to_svg(
|
|
latex: str,
|
|
template: str = "",
|
|
additional_preamble: str = "",
|
|
short_tex: str = "",
|
|
show_message_during_execution: bool = True,
|
|
) -> str:
|
|
"""Convert LaTeX string to SVG string.
|
|
|
|
Args:
|
|
latex: LaTeX source code
|
|
template: Path to a template LaTeX file
|
|
additional_preamble: String including any added "\\usepackage{...}" style imports
|
|
|
|
Returns:
|
|
str: SVG source code
|
|
|
|
Raises:
|
|
LatexError: If LaTeX compilation fails
|
|
NotImplementedError: If compiler is not supported
|
|
"""
|
|
if show_message_during_execution:
|
|
max_message_len = 80
|
|
message = f"Writing {short_tex or latex}"
|
|
if len(message) > max_message_len:
|
|
message = message[:max_message_len - 3] + "..."
|
|
print(message, end="\r")
|
|
|
|
tex_config = get_tex_config()
|
|
if template and template != tex_config["template"]:
|
|
tex_config = get_tex_template_config(template)
|
|
|
|
compiler = tex_config["compiler"]
|
|
|
|
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")
|
|
|
|
preamble = tex_config["preamble"] + "\n" + additional_preamble
|
|
full_tex = get_full_tex(latex, preamble)
|
|
|
|
# Write intermediate files to a temporary directory
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
base_path = os.path.join(temp_dir, "working")
|
|
tex_path = base_path + ".tex"
|
|
dvi_path = base_path + dvi_ext
|
|
|
|
# Write tex file
|
|
with open(tex_path, "w", encoding="utf-8") as tex_file:
|
|
tex_file.write(full_tex)
|
|
|
|
# Run latex compiler
|
|
process = subprocess.run(
|
|
[
|
|
program.split()[0], # Split for xelatex case
|
|
"-interaction=batchmode",
|
|
"-halt-on-error",
|
|
"-output-directory=" + temp_dir,
|
|
tex_path
|
|
] + (["--no-pdf"] if compiler == "xelatex" else []),
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if process.returncode != 0:
|
|
# Handle error
|
|
error_str = ""
|
|
log_path = base_path + ".log"
|
|
if os.path.exists(log_path):
|
|
with open(log_path, "r", encoding="utf-8") as log_file:
|
|
content = log_file.read()
|
|
error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
|
|
if error_match:
|
|
error_str = error_match.group()
|
|
raise LatexError(error_str or "LaTeX compilation failed")
|
|
|
|
# Run dvisvgm and capture output directly
|
|
process = subprocess.run(
|
|
[
|
|
"dvisvgm",
|
|
dvi_path,
|
|
"-n", # no fonts
|
|
"-v", "0", # quiet
|
|
"--stdout", # output to stdout instead of file
|
|
],
|
|
capture_output=True
|
|
)
|
|
|
|
# Return SVG string
|
|
result = process.stdout.decode('utf-8')
|
|
|
|
if show_message_during_execution:
|
|
print(" " * len(message), end="\r")
|
|
|
|
return result
|
|
|
|
|
|
class LatexError(Exception):
|
|
pass
|