3b1b-manim/manimlib/utils/tex_file_writing.py
2022-02-17 19:09:55 +08:00

136 lines
4.1 KiB
Python

import sys
import os
import hashlib
from contextlib import contextmanager
from manimlib.utils.directories import get_tex_dir
from manimlib.config import get_manim_dir
from manimlib.config import get_custom_config
from manimlib.logger import log
SAVED_TEX_CONFIG = {}
def get_tex_config():
"""
Returns a dict which should look something like this:
{
"executable": "latex",
"template_file": "tex_template.tex",
"intermediate_filetype": "dvi",
"text_to_replace": "YourTextHere",
"tex_body": "..."
}
"""
# Only load once, then save thereafter
if not SAVED_TEX_CONFIG:
custom_config = get_custom_config()
SAVED_TEX_CONFIG.update(custom_config["tex"])
# Read in template file
template_filename = os.path.join(
get_manim_dir(), "manimlib", "tex_templates",
SAVED_TEX_CONFIG["template_file"],
)
with open(template_filename, "r", encoding="utf-8") as file:
SAVED_TEX_CONFIG["tex_body"] = file.read()
return SAVED_TEX_CONFIG
def tex_hash(tex_file_content):
# Truncating at 16 bytes for cleanliness
hasher = hashlib.sha256(tex_file_content.encode())
return hasher.hexdigest()[:16]
def tex_to_svg_file(tex_file_content):
svg_file = os.path.join(
get_tex_dir(), tex_hash(tex_file_content) + ".svg"
)
if not os.path.exists(svg_file):
# If svg doesn't exist, create it
tex_to_svg(tex_file_content, svg_file)
return svg_file
def tex_to_svg(tex_file_content, svg_file):
tex_file = svg_file.replace(".svg", ".tex")
with open(tex_file, "w", encoding="utf-8") as outfile:
outfile.write(tex_file_content)
svg_file = dvi_to_svg(tex_to_dvi(tex_file))
# Cleanup superfluous documents
tex_dir, name = os.path.split(svg_file)
stem, end = name.split(".")
for file in filter(lambda s: s.startswith(stem), os.listdir(tex_dir)):
if not file.endswith(end):
os.remove(os.path.join(tex_dir, file))
return svg_file
def tex_to_dvi(tex_file):
tex_config = get_tex_config()
program = tex_config["executable"]
file_type = tex_config["intermediate_filetype"]
result = tex_file.replace(".tex", "." + file_type)
if not os.path.exists(result):
commands = [
program,
"-interaction=batchmode",
"-halt-on-error",
f"-output-directory=\"{os.path.dirname(tex_file)}\"",
f"\"{tex_file}\"",
">",
os.devnull
]
exit_code = os.system(" ".join(commands))
if exit_code != 0:
log_file = tex_file.replace(".tex", ".log")
log.error("LaTeX Error! Not a worry, it happens to the best of us.")
with open(log_file, "r", encoding="utf-8") as file:
for line in file.readlines():
if line.startswith("!"):
log.debug(f"The error could be: `{line[2:-1]}`")
sys.exit(2)
return result
def dvi_to_svg(dvi_file, regen_if_exists=False):
"""
Converts a dvi, which potentially has multiple slides, into a
directory full of enumerated pngs corresponding with these slides.
Returns a list of PIL Image objects for these images sorted as they
where in the dvi
"""
file_type = get_tex_config()["intermediate_filetype"]
result = dvi_file.replace("." + file_type, ".svg")
if not os.path.exists(result):
commands = [
"dvisvgm",
"\"{}\"".format(dvi_file),
"-n",
"-v",
"0",
"-o",
"\"{}\"".format(result),
">",
os.devnull
]
os.system(" ".join(commands))
return result
# TODO, perhaps this should live elsewhere
@contextmanager
def display_during_execution(message):
# Only show top line
to_print = message.split("\n")[0]
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")