2022-04-16 14:37:28 +08:00
|
|
|
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
|
2022-05-21 15:56:03 +08:00
|
|
|
import re
|
2022-05-22 23:47:45 +08:00
|
|
|
import yaml
|
2018-08-10 15:12:49 -07:00
|
|
|
|
2021-02-07 21:38:19 +08:00
|
|
|
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
|
2022-05-21 15:56:03 +08:00
|
|
|
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 = {}
|
|
|
|
|
|
|
|
|
2022-05-28 21:43:37 +08:00
|
|
|
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"):
|
2022-05-28 21:43:37 +08:00
|
|
|
result = templates_dict["basic"] + "\n" + result
|
2022-05-22 23:47:45 +08:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def get_tex_config() -> dict[str, str]:
|
2022-05-28 21:43:37 +08:00
|
|
|
"""
|
|
|
|
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:
|
2022-05-28 21:43:37 +08:00
|
|
|
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:
|
2022-05-28 21:43:37 +08:00
|
|
|
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:
|
2022-05-28 21:43:37 +08:00
|
|
|
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-21 15:56:03 +08:00
|
|
|
|
|
|
|
|
2022-05-22 23:47:45 +08:00
|
|
|
class LatexError(Exception):
|
|
|
|
pass
|